闭包
理论中的闭包
闭包是指那些能够访问自由变量的函数
自由变量:
自由变量是指在函数中使用的,但既不是函数参数,也不是函数的局部变量的变量(不存在于当前函数执行上下文中的变量对象中的变量)
即闭包由=函数+函数能够访问的自由变量
var a = 1;
function foo(){
console.log(a)
}
foo()
foo函数可以访问变量a,但是a既不是foo函数的局部变量,也不是foo函数的参数,所以a就是自由变量。
所以foo + foo函数访问的自由变量a就构成了闭包。
理论上,从技术的角度来说,所有的函数都是闭包
实践中的闭包
- 从理论角度,所有的函数都是闭包。因为它们在创建的时候就将上层上下文的数据保存起来了。(创建时的[[scope]]属性,将其上层上下文的作用域链保存下来)
- 从实践角度,要满足两个条件的函数才算闭包
- 即使创建它的上下文已经被销毁,它仍然能够访问父级上下文的变量对象
- 在代码中引用了自由变量
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();
从分析以上代码的执行上下文栈变化情况,我们就可以知道为什么checkScope执行上下文即使被摧毁了,但f函数仍然能够访问到。
- 初始化全局上下文
globalContext = {
VO:[global,scope,foo],
Scope:[globalContext.VO],
this:globalContext.VO
}
- 推入全局上下文到执行上下文栈
ECStack = [ globalContext ]
- checkscope函数被创建,[[scope]]属性被赋予为父级上下文中的作用于链
checkscope.[[scope]] = [...globalContext.Scope]
- checkscope被执行,初始化checkscope的变量对象AO,初始化checkscope的上下文,将[[scope]]赋值给作用域链,然后推入AO,再推入checkscope的上下文到执行上下文栈(这里省略了函数的分析阶段)
checkscopeContext = {
AO:{
arguments:{
length:0
},
scope:'local scope',
f: reference of f(){}
},
Scope:[AO,...[[scope]]]
}
- f函数被创建,设置其[[scope]]为父级上下文的作用域链(当前父级为checkscopeContext)
f.[[scope]] = [...checkscope.Scope]
- checkscope执行完毕,弹出checkscope上下文
ECStack.pop()
- f函数被执行,推入f函数的执行上下文,并将f函数的执行上下文初始化
fContext = {
AO:{
arguments:{
length:0
}
},
Scope:[AO,...f.[[scope]]]
}
-
根据f的作用于链Scope查找scope变量(存在于checkscopeContext.AO中),即使checkscopeContext已经被销毁,但是fContext.Scope中仍然保存着。
-
f函数执行结束,将其上下文从执行上下文栈中弹出
checkscopeContext即使被摧毁了,f函数依然可以读取到checkScopeContext.AO,这是因为f函数在创建时,它的[[scope]]属性就保存了父级的作用域链,所以f函数可以通过作用域链找到它
面试题分析
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();
答案都是3
在指向data0之前,全局上下文的VO为
globalContext = {
VO:{
data:[...],
i:3
}
}
闭包的形式:
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function closure(i) {
return function(){
console.log(i);
}
})(i);
}
data[0]();
data[1]();
data[2]();
输出0 1 2
这是由于创建data[i]函数的父级函数上下文在创建完函数后被销毁,其对象变量的i作为形参,在初始化时值已经固定。
data[0]Context = {
AO:{
arguments:{
length:0
}
},
Scope:[AO,closure[0]Context.AO,globalContext.VO]
}
在执行data[i]时,会访问对应的closure[i]Context.AO,所以是三个不同的AO对象
closure[0]Context = {
AO:{
arguments:{
0:0
length:1
},
i:0
}
}
网友评论