例如:
var outter = [];
function fun () {
for (var i = 0; i < 4; i++) {
var x = {};
x.invoke = function () {
console.log(i);
};
outter.push(x);
}
}
fun();
console.log(outter[0].invoke());
console.log(outter[1].invoke());
console.log(outter[2].invoke());
console.log(outter[3].invoke());
结果是:4 4 4 4 。fun()
执行完毕以后局部变量不是释放掉了吗?怎么outter[0].invoke()
还能访问到局部变量i
?
PHPz2017-04-10 15:05:39
经典的闭包问题
http://bonsaiden.github.io/JavaScript-Garden/zh/#function.closures
循环中的闭包
一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
上面的代码不会输出数字 0 到 9,而是会输出数字 10 十次。
当 console.log 被调用的时候,匿名函数保持对外部变量 i 的引用,此时 for循环已经结束, i 的值被修改成了 10.
为了得到想要的结果,需要在每次循环中创建变量 i 的拷贝。避免引用错误
为了正确的获得循环序号,最好使用 匿名包装器(译者注:其实就是我们通常说的自执行匿名函数)。
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。
当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。
有另一个方法完成同样的工作,那就是从匿名包装器中返回一个函数。这和上面的代码效果一样。for(var i = 0; i < 10; i++) { setTimeout((function(e) { return function() { console.log(e); } })(i), 1000) }
PHPz2017-04-10 15:05:39
你的 i 一直都在被使用中,你 fun(); 之后那个 i 就变成 4 了。
然后你之后的每一次 outter[0].invoke() 当然都是 4 了。
然后这个所谓的局部变量就永远不会被回收了,因为你的每个 outter[].invoke() 都要用到这个 i 。
正确的应该是
function fun () {
for (var i = 0; i < 4; i++) {
var x = {};
x.invoke = i;
outter.push(x);
}
}
// 或者
function fun () {
for (var i = 0; i < 4; i++) {
(function(i){
var x = {};
x.invoke = function(){
console.log(i);
};
outter.push(x);
})(i);
}
}
还有你的代码中 函数里面 为什么写那么多遍 console.log,你的 invoke() 函数不是已经有了 console.log() 的功能了?
直接 outter[1].invoke(); 不就直接输出了?
题外话:正确的使用工具才能事半功倍。
阿神2017-04-10 15:05:39
今天刚刚看到JavaScript高级程序设计(第3版)才发现书里面有个例子(7.2.1 闭包与变量)和我这个问题一样。
书中提到:作用域链的配置机制引出了一个副作用,即闭包只能取得包含函数中任何变量的最后一个值。但是可以通过创建另一个匿名函数强制让闭包的行为符合预期。
当fun()
函数执行完毕后,它的执行环境的作用域链会被销毁,但是它的活动对象仍然会留在内存中,直到匿名函数被销毁后才被销毁。
PHPz2017-04-10 15:05:39
fun
执行后:
js
funContext.AO = { i: 4, x: { invoke: <第四个 function 的引用> } }
对于每个 invoke
函数创建时:
js
invoke.[[Scope]] = [ funContext.AO, globalContext.VO ]
每个 invoke
执行时,其上下文作用域链:
js
invokeContext.Scope = [ invokeContext.AO, funContext.AO, // invoke在这里访问到标识符 i, i === 4 globalContext.VO ]