闭包
定义 一 — 内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。
定义 二 — 1. 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)2. 在代码中引用了自由变量
广义闭包:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
复习 — 函数执行过程
var scope = "global scope"
function checkscope(){
var scope = "local scope"
function f(){
return scope
}
return f
}
var foo = checkscope()
foo() // local scope
- 创建全局执行期上下文,压入执行期上下文栈
ECStack = {
globalContext
} - 全局执行期上下文初始化,全局变量赋值等
globalContext = {
GO = {
scope: undefined,
checkscope: function checkscope() {},
foo: undefined
}
} - 函数 checkscope 执行,创建函数的执行期上下文,压入执行期上下文栈
ECStack = {
checkscopeContext
globalContext
} - checkscope 函数执行期上下文初始化,变量赋值、作用域链、this
checkscopeContext {
AO: {
scope: undefined,
f: function f() {}
},
[[scope]]: [GO]
} - checkscope 执行完毕,其执行期上下文从执行期上下文栈弹出
ECStack = {
globalContext
} - f 函数执行,创建 f 的执行期上下文,压入执行期上下文栈
ECStack = {
fContext
globalContext
} - f 执行期上下文初始化
fContext = {
AO,
[[scope]]: {
checkscopeAO,
GO
}
} - f 执行完毕,f 的执行期上下文出栈
从上面的分析可以看到,在 checkscope 执行过程中,f 函数在定义时会创建 f 函数的执行期上下文,会将 checkscope 函数的作用域链拷贝给自身。此后,虽然 checkscope 函数的执行期上下文随着函数执行完毕被销毁,但 f 函数的执行期上下文中仍保有 checkscope 的作用域链,f 函数可通过自身的作用域链找到它,这就是闭包。
试一试
function test() {
var arr = [];
for (var i = 0; i < 3; i++) {
arr[i] = function () {
console.log(i);
}
}
return arr;
}
var arr = test()
arr[0]()
arr[1]()
arr[2]()
答案全为 3
- 全局执行期上下文创建并压入执行期上下文栈
ECStack = {
globalContext
} - 全局执行期上下文初始化
globalContext = {
GO = {
arr: undefined
}
} - test 函数创建执行期上下文并压入执行期上下文栈
ECStack = {
testContext
globalContext
} - test 函数执行期上下文初始化
testContext = {
AO: {
arr: undefined
},
[[scope]]: [GO]
} - test 函数执行,每个 for 循环为 arr[i] 函数创建并初始化一个执行期上下文
test 函数执行期上下文
testContext = {
AO: {
arr: [func …],
i: 3
},
[[scope]]: [GO]
}
arr[i] 函数执行期上下文
arr[i]Context = {
[[scope]]: [testContext.AO, GO]
} - test 函数执行完,其执行期上下文销毁,但 arr[i] 仍保有 test 函数的 AO
- test[i] 函数执行,由于在该函数执行期上下文中没有变量 i 故在其作用域链顶端开始向下搜索第一个变量 i,最终在 test 的 AO 中找到 i 为 3 并打印