闭包
定义
闭包是引用了自由变量的函数。
自由变量的定义
自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
举个例子
直接上代码
var data = []
for (var i = 0; i < 3; i++) {
data[i] = function() {
console.log(i)
}
}
data[0]()
data[1]()
data[2]()
从执行上下的角度来解读这几行代码如下:
- 全局上下文(
globalContext
)创建并入栈 - 创建全局上下问的AO对象
globalContext.AO = {
...otherData,
data: undefined
i: undefined
}
// 确定完之后,会将AO添加到作用域链的前端
globalContext = {
AO: 同上,
scopeChain: [AO]
}
- 执行代码赋值
i = 0
,就是说globalContext中AO的i也变成了0,碰到data[0]函数的创建,函数创建阶段会确定该函数的[[scope]]属性,此时data[0].[[scope]] = globalContext.scopeChain
- 那当代码执行到
i = 1
时也是同理globalContext中AO的i也变成了1,此时data[0].[[scope]]
中的i其实也就已经变成了1,因为他们都是指向globalContext中AO的i -
i=2
不再赘述 -
data[0]
函数即将执行,进入激活阶段,首先创建data[0]的执行上下文并入栈。 - 然后拷贝一份它的[[scope]]属性到作用域链
data[0].context = {
scopeChain: [data[0].[[scope]]]
}
- 确定它的AO对象并放置于作用域链的前端,以及确定this指向
data[0].context = {
AO: {
arguments: {length: 0}
},
scopeChain: [AO, data[0].[[scope]]],
this
}
- data[0]进入执行阶段当要打印
i
的时候,自身的AO对象中并没有,怎么办?去作用域链找!它的作用域链是本身的AO
,本身的[[scope]]
对象组成。本身的[[scope]]对象中
包含了globalContext.AO
对吧。 所以最终会在globalContext.AO
中找到i
,所以输出了3
- 在data[0]执行完毕后会出栈,data[1]又会重复上述步骤6~9。最终的结果是3个3.
如何产生我们期望的结果呢?
终于要出场了!那就是今天的主角闭包。
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]()
(function () {})() //这样的代码形式是个闭包,也称为自执行函数
这个代码在执行期间与之前不同的地方在于它会多创建一个闭包的执行上下文,在闭包的执行上下文中,保存了本次i
的数据,并且代码中没有其他因素会干扰这个值。
当执行到data[0]这个函数时,它的执行上下文的作用域链其实为
context = {
AO: {arguments: {length: 0}},
scopeChain: {
this.AO,
anonymousContext.AO, // 这个AO = {arguments: {0: 0, length: 1}, i: 0}
globalContext.AO // 这个AO = {...otherData, data: [...], i: 3}
}
}
所以当打印i
的时候,本身的AO
中还是没有,所以去去作用域链中查找,在闭包的作用域链中就已经查到了i
,所以就结束了,不会继续查找globalContext中的i
。