Home  >  Q&A  >  body text

JavaScript作用域的一个问题?

例如:

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?

伊谢尔伦伊谢尔伦2646 days ago576

reply all(5)I'll reply

  • 迷茫

    迷茫2017-04-10 15:05:39

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

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

    Reply
    0
  • PHPz

    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)
    }
    

    Reply
    0
  • PHPz

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

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

    Reply
    0
  • 阿神

    阿神2017-04-10 15:05:39

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

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

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

    Reply
    0
  • PHPz

    PHPz2017-04-10 15:05:39

    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
    ]
    

    Reply
    0
  • CancelReply