閉包是 JavaScript 一個(gè)非常重要的特性,這意味著當(dāng)前作用域總是能夠訪問外部作用域中的變量。 因?yàn)?函數(shù) 是 JavaScript 中唯一擁有自身作用域的結(jié)構(gòu),因此閉包的創(chuàng)建依賴于函數(shù)。
function Counter(start) {
var count = start;
return {
increment: function() {
count++;
},
get: function() {
return count;
}
}
}
var foo = Counter(4);
foo.increment();
foo.get(); // 5
這里,Counter 函數(shù)返回兩個(gè)閉包,函數(shù) increment 和函數(shù) get。 這兩個(gè)函數(shù)都維持著
對(duì)外部作用域 Counter 的引用,因此總可以訪問此作用域內(nèi)定義的變量 count.
因?yàn)?JavaScript 中不可以對(duì)作用域進(jìn)行引用或賦值,因此沒有辦法在外部訪問 count 變量。
唯一的途徑就是通過那兩個(gè)閉包。
var foo = new Counter(4);
foo.hack = function() {
count = 1337;
};
上面的代碼不會(huì)改變定義在 Counter 作用域中的 count 變量的值,因?yàn)?foo.hack 沒有
定義在那個(gè)作用域內(nèi)。它將會(huì)創(chuàng)建或者覆蓋全局變量 count。
一個(gè)常見的錯(cuò)誤出現(xiàn)在循環(huán)中使用閉包,假設(shè)我們需要在每次循環(huán)中調(diào)用循環(huán)序號(hào)
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
上面的代碼不會(huì)輸出數(shù)字 0 到 9,而是會(huì)輸出數(shù)字 10 十次。
當(dāng) console.log 被調(diào)用的時(shí)候,匿名函數(shù)保持對(duì)外部變量 i 的引用,此時(shí) for循環(huán)已經(jīng)結(jié)束, i 的值被修改成了 10.
為了得到想要的結(jié)果,需要在每次循環(huán)中創(chuàng)建變量 i 的拷貝。
為了正確的獲得循環(huán)序號(hào),最好使用匿名包裝器(譯者注:其實(shí)就是我們通常說的自執(zhí)行匿名函數(shù))。
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
外部的匿名函數(shù)會(huì)立即執(zhí)行,并把 i 作為它的參數(shù),此時(shí)函數(shù)內(nèi) e 變量就擁有了 i 的一個(gè)拷貝。
當(dāng)傳遞給 setTimeout 的匿名函數(shù)執(zhí)行時(shí),它就擁有了對(duì) e 的引用,而這個(gè)值是不會(huì)被循環(huán)改變的。
有另一個(gè)方法完成同樣的工作,那就是從匿名包裝器中返回一個(gè)函數(shù)。這和上面的代碼效果一樣。
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}