一、迷思!由一段代碼引發的疑惑
請看如下代碼:
復制代碼 代碼如下:
for(var i=0;i<3;i++) {
console.log(j+","+k);
for(var j=0;j<3;j++) {
var k = j+1;
}
}
console.log(i);
輸出結果:
undefined,undefined
3,3
3,3
3
如果你是搞c、java等語言的,可能你會不解,為何j、k這種局部變量可以被作用域外的代碼訪問呢?
如果JavaScript中用var聲明的變量可視為局部變量,那麼能訪問到這個變量的作用域就是這個變量的局部作用域。如上例,在console.log行處,依然有j、k的作用域,而循環外,依然有i的作用域。說到這裡,也許我可以武斷的說,JavaScript沒有真正意義的局部作用域。真的嗎?非也!
二、如何獲得真正的局部作用域呢?一個寫法引起了我的注意
大家也許看過JQuery的源碼或者Ext的源碼,也許會對下面的寫法有點熟悉。
復制代碼 代碼如下:
var a = 3,b=4;
var exports = (function() {
var a = 1,b=2;
return {a:a,b:b};
})();
console.log(""+a+","+b);
console.log(exports.a+","+exports.b);
輸出結果:
3,4
1,2
很神奇的發現(其實也不神奇,大家都知道啦)函數內部是有獨立作用域的,即函數內部var聲明的變量,僅在函數內部可以使用。所以各框架各大師都這麼寫,防止自身局部變量與外界變量(外層局部變量與全局變量)沖突。
至此,我收回第一條裡的武斷推斷,修改一下:
JavaScript以函數為界,每個函數內部擁有一個局部作用域;任何其他的塊(包括普通代碼塊,for循環、if、while等代碼塊)不存在局部作用域,使用var聲明的變量可以直接穿過這些代碼塊,可以被外部代碼訪問到。
三、何時報錯,何時undefined?var的聲明機制
看代碼:
復制代碼 代碼如下:
console.log(a)
輸出結果:
ReferenceError: a is not defined
輸出結果:
undefined
復制代碼 代碼如下:
var exports = (function() {
var a = 1,b=2;
return {a:a,b:b};
})();
console.log(a);
輸出結果:
ReferenceError: a is not defined
猜想結論:
每次JavaScript引擎執行代碼時,會先掃描作用域中的所有代碼(作用域中的function內部的代碼不會掃描),並將所有var聲明的變量記錄下來,在代碼執行到賦值之前,這些變量的值為undefined。此後如果訪問變量時,先訪問局部變量,如果沒有這個局部變量就訪問上一層的局部變量(如為閉包,上一層為閉包創建環境),直到訪問到完全局變量。如果都沒有這個變量,那麼拋出異常。
四、題外話:閉包+異步,變量值錯亂!如何確保異步情況下局部變量當前值的傳遞?
還是代碼說話:
復制代碼 代碼如下:
for(var i=0;i<3;i++) {
setTimeout(function() {
console.log(i);
},1);
}
輸出結果:
3
3
3
為何?因為在閉包異步執行的時候,i始終訪問的是外層作用域的i,由於異步了,所以在執行閉包的時候循環已經結束了,i已經為3了,故每一次打印出來的都是3。
那如何解決這個問題呢?我們需要把i轉換成局部變量。
嗯,有人會有這種寫法:
復制代碼 代碼如下:
for(var i=0;i<3;i++) {
var j = i;
setTimeout(function() {
console.log(j);
},1);
}
輸出結果:
2
2
2
為何?
其實之前已經解釋過了,其實j和i的作用域是一樣的。都是外層局部變量,在異步情況下循環執行完成的時候j為2(比i少一次i++);
那該怎麼辦呢?(請想象某廣告,(⊙v⊙))。
大家知道,函數中的參數也算函數的局部變量。那麼這裡有一個辦法,可以將局部變量轉換為函數的實參,這樣就達到值傳遞的效果了。
復制代碼 代碼如下:
for(var i=0;i<3;i++) {
setTimeout((
function(j){
return function() {
console.log(j);
}
})(i)
,1);
}
輸出
0
1
2
其實說了這麼多,代碼寫出來大家就差不多明白了吧,用這種匿名函數的方式去除了異步情況下變量變化的問題,不過此為本貼的題外話了。
總結:
額。不寫了,我懶,哪天抽空補上。嘿嘿。
其實這些結論RFC中應該都寫了吧。但是啃英文文檔。。。還是算了。。自己推斷了。哈哈莫見笑莫見笑