闭包指有权访问另一个函数作用域中的变量。
var x = 10
function fn(y) {
var z = 20
function sum() {
return x + y + z
}
return sum
}
fn(1)()
分析:
- 开始执行代码,创建全局执行上下文,推入执行上下文栈。
- 执行函数
fn
,创建fn
的执行上下文,推入执行上下文栈。 - 执行完
fn
,返回函数sum
- 执行栈弹出
fn
的执行上下文,并销毁。 - 继续执行
sum
, 创建sum
的执行上下文,推入执行上下文栈 - 在代码中,需要拿到变量
x
,y
,z
,且在当前执行上下文中并没有该变量,z
和y
需要在上级函数fn
中查找,而x
需要在全局环境中找。 - 但上级执行上下文已经被弹出栈且销毁了,实际上因为上级的执行上下文的活动对象中有被引用,该活动对象不会被销毁,还存留在内存中。所以这时候就可以通过作用域链找到上级执行上下文的活动对象(fnContext.VO)和上上级的活动对象(globalContext.VO)。就可以拿到对应的变量。
此时的作用域链为:sumContext.Scope = [sumContext.VO, fnContext.VO, globalContext.VO]
,通过该作用域链就可以找到对应值。 - 找到变量后进行计算后返回,函数执行完毕,弹出执行上下文,销毁。
- 接着销毁
fnContext.VO
活动对象。
闭包与变量
闭包只能取得包含(外部)函数中任何变量的最后一个值。原因是当外部函数执行时会对该活动对象进行修改,所以当执行完后就是最后的值。
function fn() {
var result = []
for(var i=0; i<10; i++){
result[i] = function() {
return i
}
}
return result
}
该函数看起来会返回一个数组,并且每个索引对应的函数返回当前的索引的值。但实际上每个函数返回的都是10。
原因是每个函数都保存对着外部函数 fn
的活动对象的引用,而 fn
执行完毕后,i
的值是10,在数组中的匿名函数拿到的 i
值就是外部函数 fn
的活动对象里的变量 i = 10
,所以最后都返回10。
将代码修改成如下:
function fn() {
var result = []
for(var i=0; i<10; i++){
result[i] = (function(num) {
return function() {
return num
}
})(i)
}
return result
}
此时,得到了我们想要的结果,在中间加了一个自执行匿名函数并将索引 i 传递给函数,函数的参数都是值传递的,所以在最终返回的数组函数中的外部函数里拿到的就是每个循环的索引值。