閉包是前端開(kāi)發(fā)中的一個(gè)重要概念弊仪,也是前端面試的必問(wèn)問(wèn)題之一护赊。對(duì)于JavaScript初學(xué)者而言,閉包學(xué)習(xí)JavaScript的一大障礙。網(wǎng)上有很多閉包的教程已维,形象地告訴了我閉包長(zhǎng)什么樣。但是大部分教程沒(méi)有對(duì)閉包的定義給出精準(zhǔn)的表達(dá)险耀,也沒(méi)有對(duì)閉包背后的一些原理和邏輯進(jìn)行解釋案怯。本文通過(guò)整合網(wǎng)上各路資料,對(duì)閉包前前后后的知識(shí)點(diǎn)進(jìn)行梳理江场,希望可以幫助大家準(zhǔn)確并且深刻理解閉包的概念纺酸。(本文假設(shè)大家對(duì)閉包有一定的理解)
Scope
要理解閉包,先要理解一個(gè)重要概念—作用域址否。
In computer programming, the scope of a name binding – an association of a name to an entity, such as a variable – is the region of a computer program where the binding is valid: where the name can be used to refer to the entity.
Such a region referred to as is a scope block.
scope又可以分為詞法作用域(Lexical scope)和動(dòng)態(tài)作用域(Dynamic scope)餐蔬。兩者區(qū)別與對(duì)區(qū)域這個(gè)概念的解讀。Wiki百科對(duì)兩者的解釋如下:
In languages with lexical scope (also called static scope), name resolution depends on the location in the source code and the lexical context, which is defined by where the named variable or function is defined. In contrast, in languages with dynamic scope the name resolution depends upon the program state when the name is encountered which is determined by the execution context or calling context.
在詞法作用域中佑附,一個(gè)name是否有效取決于它在源代碼中的位置樊诺,也就是詞法上下文。而動(dòng)態(tài)作用域要相對(duì)復(fù)雜一點(diǎn)音同,在動(dòng)態(tài)作用域中词爬,一個(gè)name是否有效取決于這個(gè)程序的運(yùn)行時(shí)狀態(tài),也就是運(yùn)行時(shí)上下文权均。
對(duì)詞法作用域在JavaScript中的表現(xiàn)在本文不作闡述顿膨,具體參考這篇博文:深入理解javascript原型和閉包(12)——簡(jiǎn)介【作用域】
對(duì)Closure的一些定義
各種專業(yè)文獻(xiàn)上的"閉包"(closure)定義非常抽象,很難看懂螺句。我的理解是虽惭,閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
A closure is the combination of a function and the lexical environment within which that function was declared.
參考自MDN Closure
MDN的定義指出了閉包需要的東西:閉包 = 函數(shù) + 函數(shù)定義的詞法上下文環(huán)境蛇尚。阮一峰老師的定義指出了閉包產(chǎn)生的現(xiàn)象:一個(gè)函數(shù)能夠讀取其他函數(shù)內(nèi)部變量芽唇。
In programming languages, closures (also lexical closures or function closures) are techniques for implementing lexically scoped name binding in languages with first-class functions.
wiki百科上的定義指出了閉包需要的語(yǔ)言條件: first-class functions。關(guān)于這個(gè)知識(shí)點(diǎn)可以參考“函數(shù)是一等公民”背后的含義取劫。另外匆笤,定義中提到的implementing lexically scoped name binding ,即基于詞法作用域的name綁定與scope中的binding概念相互照應(yīng)谱邪。本質(zhì)上就是說(shuō)的就是詞法作用域與變量有效性的關(guān)系炮捧。
在JavaScript中,實(shí)現(xiàn)外部作用域訪問(wèn)內(nèi)部作用域中變量的方法叫做閉包惦银。
參考自《深入淺出Node.js》
以上對(duì)閉包的定義都略有差別咆课,有的將閉包定義為函數(shù)末誓,有的將閉包定義為方法,也有將閉包定義為組合书蚪。我覺(jué)得將閉包理解為一個(gè)方法喇澡,或者某個(gè)東西都對(duì)。兩種定義的方法都對(duì)我們理解閉包有幫助殊校。
JavaScript的閉包
我們都會(huì)遇到在一個(gè)外部函數(shù)套著一個(gè)內(nèi)部函數(shù)的情況晴玖,比如說(shuō):
function foo(x) {
var tmp = 3;
function b(y) {
alert(x + y + (++tmp));
}
b(2);
b(3);
}
foo(0);
在foo函數(shù)結(jié)束的時(shí)候,tmp就會(huì)被銷(xiāo)毀为流。一般來(lái)說(shuō)呕屎,當(dāng)內(nèi)部函數(shù)被return的時(shí)候,外部就可以引用內(nèi)部的函數(shù)敬察,閉包就會(huì)通過(guò)return而產(chǎn)生秀睛。如:
function foo(x) {
var tmp = 3;
return function (y) {
alert(x + y + (++tmp));
}
}
var bar = foo(2); // bar 現(xiàn)在是一個(gè)閉包
bar(10);
按照我們?cè)镜睦斫猓跊](méi)有閉包的情況下莲祸,foo函數(shù)執(zhí)行完琅催,它內(nèi)部的tmp變量就會(huì)被銷(xiāo)毀,但是因?yàn)橥獠亢瘮?shù)引用了內(nèi)部的變量產(chǎn)生了閉包虫给,內(nèi)部函數(shù)的詞法上下文沒(méi)有被銷(xiāo)毀,tmp變量也沒(méi)有被銷(xiāo)毀侠碧。
當(dāng)然抹估,也有不用閉包的return的例子,比如利用setInterval或者綁定一個(gè)事件等等方法:
function a(){
var temp = 0;// let也可以
function b(){
console.log(temp++);
}
// setInterval可以產(chǎn)生閉包
setInterval(b,1000);
// 綁定可以產(chǎn)生閉包
window.addEventListener('click',b);
// ajax傳入callback可以產(chǎn)生閉包
ajax(b);
// 或者直接把這個(gè)函數(shù)傳給window或者其它函數(shù)外部的元素
window.closure = b;
}
a();
可以看到弄兜,只要內(nèi)部函數(shù)有機(jī)會(huì)在函數(shù)外部被調(diào)用药蜻,或者說(shuō)內(nèi)部函數(shù)被外部的某個(gè)變量引用,就會(huì)產(chǎn)生閉包替饿。就像《深入淺出Node.js》中提到的那樣:
閉包是JavaScript中的高級(jí)特性语泽,利用它可以產(chǎn)生很多巧妙的效果。它的問(wèn)題在于视卢,一旦有變量引用了這個(gè)中間函數(shù)踱卵,這個(gè)中間函數(shù)不會(huì)釋放,同時(shí)也使得原始作用域不會(huì)得到釋放据过。作用域中產(chǎn)生的內(nèi)存占用也不會(huì)被釋放惋砂。除非不再有引用,才會(huì)逐步釋放绳锅。
參考自 《深入淺出Node.js》
參考資料
動(dòng)態(tài)作用域和詞法域的區(qū)別是什么西饵?
“函數(shù)是一等公民”背后的含義
js閉包的概念作用域內(nèi)存模型
阮一峰 學(xué)習(xí)Javascript閉包(Closure)
javascript基礎(chǔ)拾遺——詞法作用域
深入理解javascript原型和閉包(12)——簡(jiǎn)介【作用域】