作用域閉包
function foo() {
var a = 2;
function bar () { //bar()的詞法作用域能夠訪問foo()的內(nèi)部作用域
console.log(a);
}
return bar; //然后將bar()函數(shù)本身當(dāng)做一個(gè)值進(jìn)行傳遞
}
var baz = foo(); //foo()執(zhí)行后艺智,其返回值(即內(nèi)部的bar()函數(shù))賦值給變量baz
baz(); //2 —— 閉包的效果
通常來說,foo()函數(shù)執(zhí)行后,其整個(gè)內(nèi)部作用域都會(huì)被銷毀(垃圾回收機(jī)制)互广,而閉包的“神奇”之處正是可以阻止這件事發(fā)生。
bar()依然持有對(duì)foo()內(nèi)部作用域的引用卧土,這個(gè)引用就叫做閉包惫皱。
閉包使得函數(shù)可以繼續(xù)訪問定義時(shí)的詞法作用域。
循環(huán)和閉包
預(yù)期希望分別輸出數(shù)字1-5尤莺,每秒一次旅敷,每次一個(gè);
但實(shí)際上颤霎,代碼運(yùn)行時(shí)會(huì)已每秒一次的頻率輸出5次6(6產(chǎn)生的原因:循環(huán)的終止條件是i不再<= 5媳谁,條件首次成立時(shí)的值是6.故輸出顯示的是循環(huán)結(jié)束時(shí)i的最終值。)友酱。
for (var i = 1; i <= 5; i++) {
setTimeout(function timer(){
console.log(i);
},i * 1000);
};
根據(jù)作用域的工作原理晴音,實(shí)際情況為:盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭代中分別定義的,但他們都被封閉在一個(gè)共享的全局作用域中缔杉,因此實(shí)際上只有一個(gè)i锤躁。
故我們需要更多的閉包作用域,特別是在循環(huán)過程中每個(gè)迭代都需要一個(gè)閉包作用域或详。
方法一:用IIFE產(chǎn)生閉包解決
IIFE(立即執(zhí)行函數(shù))會(huì)通過聲明并立即執(zhí)行一個(gè)函數(shù)來創(chuàng)建作用域系羞。但若作用域?yàn)榭展疲瑑H將其封閉是不夠的,故IIFE需要有自己的變量觉啊,用來在每個(gè)迭代中儲(chǔ)存i的值:
for (var i = 1; i <= 5; i++) {
(function(){
var j = i; //IIFE需要有自己的變量拣宏,用來在每個(gè)迭代中儲(chǔ)存i的值
setTimeout (function timer (
console.log(j);
),j * 1000);
})();
}
將這段代碼進(jìn)行改進(jìn)之后:
for (var i = 1; i <= 5; i++) {
(function(j){ //IIFE需要有自己的變量,用來在每個(gè)迭代中儲(chǔ)存i的值
setTimeout (function timer ( //在迭代內(nèi)使用IIFE會(huì)為每個(gè)迭代都生成一個(gè)新的作用域
console.log(j); //使得setTimeout函數(shù)的回調(diào)可以將新的作用域封閉在每個(gè)迭代的內(nèi)部
),j * 1000);
})(i);
}
方法二:let
for (var i = 1; i <= 5; i++) {
let j = i; // 用let聲明可以劫持塊作用域杠人,并在塊作用域中聲明一個(gè)變量
setTimeout(function timer(){
console.log(j);
},j * 1000);
}
或者直接在for循環(huán)頭部用let聲明:
for (let i = 1; i <= 5; i++) {
setTimeout(function timer(){
console.log(i);
},i * 1000);
}