MDN對閉包的定義:
閉包是指那些能夠訪問自由變量的函數(shù)
那什么是自由變量?
自由變量是指在函數(shù)中使用的运挫,但既不是函數(shù)參數(shù)也不是函數(shù)的局部變量的變量
所有旗们,閉包 = 函數(shù) + 函數(shù)能訪問到的自由變量
例如:
var a = 1
function foo() {
console.log(a)
}
foo()
foo函數(shù)可以訪問變量a廷痘,但是a既不是函數(shù)參數(shù)也不是foo函數(shù)的局部變量,所有a是局部變量件已。
所以foo函數(shù)+ foo函數(shù)能訪問的自由變量a就構成了一個閉包笋额,但是和以前理解的閉包不太一樣。
上述說的閉包是理論閉包篷扩,其實還有一個實踐角度上的閉包:
ECMAScript中兄猩,閉包是指
1、從理論角度:所有的函數(shù)鉴未。因為它們都在創(chuàng)建的時候就將上層的上下文的數(shù)據(jù)保存起來了枢冤。哪怕是簡單的全局變量也是如此,因為函數(shù)中訪問全局變量就相當于是在訪問自由變量铜秆,這個時候使用為外層的作用域淹真。
2、從實踐角度:以下函數(shù)才算是閉包:
(1)即使創(chuàng)建它的上下文已經(jīng)銷毀连茧,它既然存在(比如核蘸,內部函數(shù)從父函數(shù)中返回)
(2)在代碼中引用了自由變量
分析
var scope = 'global scope'
function checkscope() {
var scope = 'local scope'
function f() {
return scope
}
return f
}
var foo = checkscope()
foo()
分析下上述代碼的執(zhí)行過程:
- 進入全局代碼,創(chuàng)建全局執(zhí)行上下文啸驯,全局執(zhí)行上下文壓入執(zhí)行上下文棧
- 全局執(zhí)行上下文初始化
- 執(zhí)行checkscope函數(shù)客扎,創(chuàng)建checkscope函數(shù)執(zhí)行上下文,checkscope執(zhí)行上下文被壓入執(zhí)行上下文棧
- checkscope執(zhí)行上下文初始化坯汤,創(chuàng)建變量對象虐唠、作用域鏈、this
- checkscope函數(shù)執(zhí)行完畢惰聂,checkscope執(zhí)行上下文從執(zhí)行上下文棧中彈出
- 執(zhí)行f函數(shù)疆偿,創(chuàng)建f函數(shù)執(zhí)行上下文,f執(zhí)行上下文被壓入執(zhí)行上下文棧
- f執(zhí)行上下文初始化搓幌,創(chuàng)建變量對象杆故、作用域鏈、this
- f函數(shù)執(zhí)行完畢溉愁,f函數(shù)上下文從執(zhí)行上下文棧中彈出
當了解了上述的過程之后处铛,我們會發(fā)現(xiàn)一個問題,當f函數(shù)執(zhí)行的時候拐揭,checkscope函數(shù)上下文已經(jīng)被銷毀了(執(zhí)行上下文棧中彈出)撤蟆,怎么還能讀取到checkscope中的scope的值呢?
當我們了解了具體的執(zhí)行過程后堂污,我們知道f執(zhí)行上下文維護了一個作用域鏈:
fContext={
Scope: [AO, checkscopeContext.AO, globalContext.VO]
}
就是因為這個作用域鏈家肯,f函數(shù)依然可以讀取到checkscopeContext.AO的值,說明當f函數(shù)引用了checkscopeContext.AO中的值的時候盟猖,即使checkscopeContext被銷毀了讨衣,但是JavaScript依然迥然checkscopeContext.AO活在內存中换棚,f函數(shù)依然可以通過f函數(shù)的作用域鏈找到它,正是因為JavaScript做到了這一點反镇,從而實現(xiàn)了閉包這個概念固蚤。
必刷題分析
var data = []
for(var i=0;i<3;i++) {
data[i] = function() {
console.log(i)
}
}
data[0]()
data[1]()
data[2]()
答案都是3,來分析一個原因
當執(zhí)行到data[0]之前歹茶,此時的全局上下文VO為:
globalContext ={
VO: {
data: [...],
i: 3
}
}
當執(zhí)行data[0]函數(shù)的時候夕玩,data[0]函數(shù)的作用域鏈為:
data[0]Context: {
Scope: [AO, globalContext.VO]
}
data[0]Context的AO并沒有i的值,所以會從globalContext.VO中去找辆亏,i為3风秤,所以打印結果為3
來看看閉包的實現(xiàn)方式:
var data = []
for(var i=0;i<3;i++) {
data[i] = (function(i) {
return function() {
console.log(i)
}
})(i)
}
data[0]()
data[1]()
data[2]()
當執(zhí)行data[0]函數(shù)的時候鳖目,data[0]函數(shù)的作用域鏈為:
data[0]Context: {
Scope: [AO, 匿名函數(shù)Context.VO, globalContext.VO]
}
匿名函數(shù)執(zhí)行上下文的AO為:
匿名函數(shù)Context = {
AO: {
arguments: {
0: 0,
lenght: 1
},
i: 0
}
}
data[0]Context的AO并沒有i值扮叨,所有會沿著作用域鏈從匿名函數(shù)Context.AO中查找,這時候就會找i為0领迈,找到了就不會往globalContext.VO中查找了彻磁,即使globalContext.VO也有i的值(值為3),所有打印為0