JavaScript作用域的一个问题?
伊谢尔伦
伊谢尔伦 2017-04-10 15:03:39
0
5
639

例如:

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?

伊谢尔伦
伊谢尔伦

小伙看你根骨奇佳,潜力无限,来学PHP伐。

reply all(5)
迷茫

牵扯到js中函数作用域链和闭包的问题哦。

如果闭包对函数中的一个对象未来有引用的话,这个对象不会被释放哦

PHPzhong

经典的闭包问题
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)
}
PHPzhong

你的 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(); 不就直接输出了?

题外话:正确的使用工具才能事半功倍。

阿神

今天刚刚看到JavaScript高级程序设计(第3版)才发现书里面有个例子(7.2.1 闭包与变量)和我这个问题一样。

书中提到:作用域链的配置机制引出了一个副作用,即闭包只能取得包含函数中任何变量的最后一个值。但是可以通过创建另一个匿名函数强制让闭包的行为符合预期。

fun()函数执行完毕后,它的执行环境的作用域链会被销毁,但是它的活动对象仍然会留在内存中,直到匿名函数被销毁后才被销毁。

PHPzhong

fun执行后:

jsfunContext.AO = {
    i: 4,
    x: {
        invoke: <第四个 function 的引用>
    }
}

对于每个 invoke 函数创建时:

jsinvoke.[[Scope]] = [
    funContext.AO,
    globalContext.VO
]

每个 invoke 执行时,其上下文作用域链:

jsinvokeContext.Scope = [
    invokeContext.AO,
    funContext.AO, // invoke在这里访问到标识符 i, i === 4 
    globalContext.VO
]
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template