JavaScript 作為Web 開(kāi)發(fā)中重要的一部分,讓網(wǎng)頁(yè)頁(yè)面的交互效果更加豐富,JavaScript 之閉包是 JS 中的一個(gè)重要概念,由于其特性的神奇性,即使是一些已經(jīng)有了一定經(jīng)驗(yàn)的開(kāi)發(fā)者在面對(duì)閉包時(shí),也不免感到一定的困惑,下面,我們將會(huì)來(lái)詳細(xì)探究一下閉包的本質(zhì)以及如何應(yīng)用。
什么是閉包呢?我們可以先看一下多人議論最多的一個(gè)例子:for 循環(huán)嵌套 setTimeout 函數(shù)的經(jīng)典問(wèn)題:
for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i);
}, 100);
}
這個(gè)代碼片段的輸出結(jié)果是,十次輸出的都是 10,那是為什么呢?這里其實(shí)是 JS 變量作用域問(wèn)題所導(dǎo)致的,當(dāng) for 循環(huán)中的 setTimeout 函數(shù)執(zhí)行時(shí),i 的值已經(jīng)是 10了,因?yàn)楫?dāng) setTimeout 函數(shù)執(zhí)行時(shí),for 循環(huán)已經(jīng)執(zhí)行完畢,所以很多的開(kāi)發(fā)者在這里將 setTimeout 函數(shù)改寫(xiě)為:
for(var i=0;i<10;i++){
(function(j){
setTimeout(function(){
console.log(j);
}, 100);
})(i);
}
這個(gè)代碼片段的輸出結(jié)果是遞增的 1,2,3,4 到 10,這里就用到了 JS 中的閉包,函數(shù)內(nèi)部可以引用外部的變量,而外部的變量,由于被函數(shù)內(nèi)部的變量引用,所以不能被垃圾回收機(jī)制回收。
再來(lái)看一個(gè) WEB 開(kāi)發(fā)中類似的問(wèn)題:
var account =0;
function getAccount(){
var account = 100;
return function(){
return account;
}
}
console.log(getAccount()());//輸出結(jié)果為100
這里 getAccount() 的返回值是一個(gè)函數(shù),內(nèi)部引用了外部的 account 變量,也就是閉包,即使 getAccount 函數(shù)執(zhí)行結(jié)束,其內(nèi)部的 account 變量也得以保存下來(lái),這樣的作用就可以讓他們?cè)谕鈱幼饔糜蛑欣^續(xù)存在。
閉包不僅局限于函數(shù),即使是 if、for 語(yǔ)句中的代碼塊也是同樣適用的。我們來(lái)看一個(gè)例子:
function fun(n,o){
console.log(o);
return function(m){
return function(q){
console.log(n+m+q+o);
};
};
}
fun(1,2)(3)(4);
這個(gè)例子執(zhí)行的輸出結(jié)果是 2 、10(1+3+4+2),這里的 fun 函數(shù)返回了一個(gè)函數(shù),而這個(gè)函數(shù)里又嵌套了一個(gè)函數(shù),那么函數(shù)內(nèi)部引用的 n 和 o 值,即使在內(nèi)部函數(shù)執(zhí)行后,仍然存在于內(nèi)存中,因?yàn)檫@兩個(gè)值已經(jīng)作為變量的參數(shù)被傳入到新函數(shù)中,所以這里的運(yùn)行結(jié)果可以在函數(shù)嵌套執(zhí)行完成后輸出。
除此之外,還有一個(gè)函數(shù)緩存的問(wèn)題,JavaScript 中的函數(shù)也可以作為一個(gè)對(duì)象來(lái)使用,所以可以很好的緩存數(shù)據(jù),例如:
function add(){
var args = Array.prototype.slice.call(arguments);
var fn = function(){
var arg_fn = Array.prototype.slice.call(arguments);
return add.apply(null,args.concat(arg_fn));
};
fn.toString=function(){
return args.reduce(function(pre,curval){
return pre+curval;
});
};
return fn;
}
console.log(add(1)(2)(3));//輸出結(jié)果為6
這個(gè)小例子就將函數(shù)內(nèi)部的 add 函數(shù)緩存了起來(lái),在下一次執(zhí)行的時(shí)候,仍然可以使用該函數(shù),而不是去重新生成。
綜上所述,閉包真的很重要,所以如果能在以后的開(kāi)發(fā)中遇到比較恰當(dāng)使用閉包的機(jī)會(huì),也一定要拿好這把利器,去為項(xiàng)目帶來(lái)更深度的優(yōu)化。