function fun(){
for(var i=0; i<lis.length; i++){ //此處的length=5
lis[i].onclick = function(){
console.log(i);
}
}
}
複製代碼
因而我在console裏寫入瞭如上代碼,依次點擊lis,輸出了五次4,這對於寫慣了c語言的我是一個觀念上的顛覆,因而開始了大規模的資料查找,試圖解決個人這個疑惑。html
"在這個函數裏面的i其實引用的是最後一次i的值,爲何不是1,2,3,4...呢? 由於for循環中並無執行這個函數,這個函數是在你點擊的時候才執行的,當執行這個函數的時候,它發現它本身沒有這個變量i,因而向它的做用域鏈中查找這個變量i,由於當你單擊這個box的時候已經for循環完了,因此找到的i是最後一次賦值後的i"es6
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
var funcs = createFunctions();
for (var i=0; i < funcs.length; i++){
console.log(funcs[i]());
}
複製代碼
陷阱就是:函數帶()纔是執行函數! 單純的一句 var f = function() { alert('Hi'); }; 是不會彈窗的,後面接一句 f(); 纔會執行函數內部的代碼。上面代碼翻譯一下就是:bash
var result = new Array(), i;
result[0] = function(){ return i; }; //沒執行函數,函數內部不變,不能將函數內的i替換!
result[1] = function(){ return i; }; //沒執行函數,函數內部不變,不能將函數內的i替換!
...
result[9] = function(){ return i; }; //沒執行函數,函數內部不變,不能將函數內的i替換!
i = 10;
funcs = result;
result = null;
console.log(i); // funcs[0]()就是執行 return i 語句,就是返回10
console.log(i); // funcs[1]()就是執行 return i 語句,就是返回10
...
console.log(i); // funcs[9]()就是執行 return i 語句,就是返回10
複製代碼
"爲何只垃圾回收了 result,但卻不收了 i 呢? 由於 i 還在被 function 引用着啊。比如一個餐廳,盤子老是有限的,因此服務員會去巡臺回收空盤子,但還裝着菜的盤子他怎麼敢收? 固然,你本身手動倒掉了盤子裏面的菜(=null),那盤子就會被收走了,這就是所謂的內存回收機制。"閉包
「做用域鏈的機制引出了一個值得注意的反作用,即閉包只能取得包含函數中任何變量的最後一個值。」函數
「表面上看,每一個函數都應該返回本身對應的i值,但實際上每一個函數都返回了同樣的值。由於每一個函數的做用域鏈中都保存着fun()函數的活動對象,因此他們引用的都是同一個變量i。當fun()函數返回後,變量的i值是4,此時每一個函數都引用着保存變量i的同一個變量對象,因此在每一個函數內部i的值都是10。」ui
function fun(){
for(var i=0; i<lis.length; i++){ //此處的length=5
lis[i].onclick = (function(num){
return function(){
console.log(num);
}
})(i)
}
}
複製代碼
這種方法在每次循環中,用當即執行的匿名函數記錄下了當前的i值(num),並建立了單獨的做用域,又在匿名函數中建立了一個新的閉包,接收i(num)值,造成了單獨的做用域鏈。spa
function fun(){
for(let i=0; i<lis.length; i++){ //此處的length=5
lis[i].onclick = function(){
console.log(i);
}
}
}
複製代碼
此方法的成功,最大的功臣即是let的塊級做用域特色,他在每次循環中生成了單獨的做用域,達到了與上一種方法相同的效果。翻譯
研究這個看似很簡單的特性耗費了整整一天的時間,也深深體會到了爲何說JS語言的糟粕很多。設計