一、迷思!由一段代碼引發的疑惑
請看如下代碼:
複製代碼 代碼如下: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中應該都寫了吧。但是啃英文文檔。。。還是算了。。自己推斷了。哈哈莫見笑莫見笑