JavaScript拼图:范围

时间:2012-01-30 19:32:11

标签: javascript variables scope

所以我想弄清楚这个难题:

function fun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        result.push( function() {return i} );
    }
    return result;
}

console.log(fun1()[0]()) // returns 5?

该数组的第一个元素是否应该返回一个返回'0'的函数?

6 个答案:

答案 0 :(得分:6)

不,它应该返回5.

该函数仍然具有对i的引用,在for之后为5。

答案 1 :(得分:6)

这是因为你的函数(闭包)维持了一个

  • i
  • 的引用

而不是

  • 每个特定迭代期间存在的i快照

这意味着您的函数在执行时将知道当前i的当前值。因为循环已经在那时完成,所以这将是循环设置的i的最后一个值。那么,我们如何获取快照而不是引用?幸运的是,数字参数由传递...因此,您可以通过将i传递给第二个函数来避免您的问题...(fiddle):

function fun1(){
    var result = [];
    for (var i = 0; i < 5; i++){
        (function(x){
            result.push(function() { 
                return x; // x now has i's snapshotted value
            });
        })(i); // passes i by value since it's numeric
    }
    return result;
}

alert(fun1()[0]())

[编辑] 让我们从等式中取出循环(fiddle):

var i = 1;
var fn = function() { // this closure maintains a reference to i 
    alert(i); 
};
i = 10; // such that when we change i's value here
fn(); // it's reflected by the function call: alerting 10

所以,要“修复”这个(fiddle):

var i = 1;
var fn = (function(x) { // this is a self-executing anonymous function
    return function() { // this closure maintains a reference to x
        alert(x); 
    };
})(i); // we're passing i by value such that x will retain that value
i = 10; // when we change i here, it has no affect on x
fn(); // this will alert 1

答案 2 :(得分:3)

让我们分解一步一步发生的事情:

  • 我们声明一个返回数组fun1()的函数result

  • for循环迭代5次,每次迭代递增i

  • 请注意,在整个执行过程中,返回i的匿名函数未被调用

  • for循环结束时i的值为5

  • 调用fun1()[0]会返回一个数组result[],它有一个将引用存储到i的函数,而不是 of i

  • 调用匿名函数,然后跟随该引用并返回i的值5

答案 3 :(得分:2)

你应该阅读一些关于Javascript闭包的内容,仔细查看。

来自i函数的变量fun1可以从循环中的函数访问,但它是相同的变量,而不是克隆。

答案 4 :(得分:2)

内部函数中“i”的值将是返回时外部函数中“i”的值。因此,数组中的所有5个元素在调用时都将返回“5”。

答案 5 :(得分:1)

真是个问题!

问题是,在javascript中,就像在java中一样,整数值是按值传递的,而不是通过引用传递的。这是其他所有人告诉你的。但我假设你不知道这意味着什么。它是跨语言的标准,所以让我们看看我是否可以解释!

注意:这是一个残缺的解释。我真的不知道javascript是如何传递价值的,以及有趣的术语,如&#34; stack&#34;和&#34;堆&#34;和&#34;通过参考值&#34; (我认为在技术上是正确的吗?)可以提供更多的学术答案。希望我可以跳过这一切,并假设

  1. 有一个地方,计算机存储程序的内存(没有&#39; t)
  2. 它以十六进制表示法存储在RAM上(也许它确实存在?我不知道)
  3. 我们只传递引用和传递值(更多关于......现在!)
  4. 因此,我们有pass-by-reference和pass-by-value。 在程序的内存中,当通过值时,变量&#34; i&#34;指向是一个字面数字。 当你通过引用传递时,变量&#34; i&#34;会指向某个其他对象的内存位置。

    这是什么意思?

    line 1. i = new Object();
    line 2. i = new Object();
    
    在第1行之后,该计划将在记忆中留下一点回家,并且它会给出一个位置 - 我不知道,有些公羊价值(比如说。我不喜欢&#34; i&#34; #39;真的知道引擎盖下的javascript)。然后,在该值中,它将放入另一个值。所以我们有:

    i -> 0x67 -> 0x68
    

    所以程序知道,当它看到值&#34; i&#34;时,它会进入内存位置0x67,并获得值0x68。它还知道该值指向另一个内存位置。在这种情况下我们有

    ...
    0x67 -> 0x68 (in bytes)
    0x68 -> some byte representation of a new Object()
    0x69 -> i don't know! some more bytes. they're not important right now.
    ...
    

    第2行开始后,我们

    i ->0x67 -> 0x69 (in bytes)
    

    0x69 -> some byte representation of a new Object()
    

    给予:

    ...
    0x67 -> 0x68 (in bytes)
    0x68 -> some byte representation of a new Object()
    0x69 -> some byte representation of a new Object()
    ...
    

    现在,碰巧,如果你在两个新的Ojbects上如此平等,那么他们就好了(我认为)。 这里要注意的重要一点是,在第二行之后,i的值已经从第一个存储位置改变到第二个存储位置。

    所以,如果你这样做了:

    i = new Object()
    j = i
    i = new Object()
    
    你会得到:

    i -> 0x67 -> 0x68 ->byte for a new Object
    j -> 0x69 -> 0x68 -> byte for a new Object
    i -> 0x67 -> 0x70 -> byte for another new Object. Same bytes as above.
    

    请参阅? &#34; J&#34;得到&#34; i&#34;中的实际字节值,这意味着&#34; j&#34;获取值0x68,这是新对象的内存位置。在第三行之后,j具有第一个对象的位置,并且&#34; i&#34;具有第二个对象的位置。

    但是,你对j做的任何改变 - 比如说

    j.x = "moo"
    

    不会出现在&#34; i&#34;指着。它只会出现在&#34; j&#34;宾语。因为,请记住,j指向与我指向的对象完全不同的位置的完全不同的对象。

    好的, 让我们放弃,让我们回归到价值传递。

    下面

    i = 6;
    j = i;
    i = 7;
    

    所以我们得到了

    i -> 0x67 -> 6 (well, the bytes for 6, anyway)
    j -> 0x68 -> 6 (again, bytes for 6. the same bytes. We're pointing to i here)
    i -> 0x67 -> 7
    

    并且,如果我们要看j?

    j -> 0x68 -> 6
    

    所以,因为我们更新了&#34; i&#34;,我们也不会更新&#34; j&#34;,得到它?这些字节被保存,j在这里得到它自己的字节副本。

    好的,所以如上所述,请查看您的代码:

    function fun1(){
        var result = [];
        for (var i = 0; i < 5; i++){
            result.push( function() {return i} );
        }
        return result;
    }
    

    是的,所以在for循环中,你创建的函数包含对i 的引用,for循环的动作是递增i。

    你很可能会坚持下去!&#34;什么 - 你觉得这样的内心吗? - 因为我刚才注意到数据是如何通过整数值(或一般数字)传递的。这里的问题是:

    从此页面: https://developer.mozilla.org/en/JavaScript/Guide/Closures

      

    闭包是一种特殊的对象,它结合了两个东西:一个函数,以及创建该函数的环境。环境由创建闭包时在范围内的任何局部变量组成。

    所以......我粘贴了上面的原因 - 闭包是一个函数(fun1,比如说)和创建函数的环境。和环境?它由范围内的局部变量(噢,我怎么样?)组成。

    所以,直言不讳......正如我仍然在整个循环的范围内,同样的&#34;我&#34;是我们生成的每个闭包的范围。那我呢?我正在更新它的价值。您没有传递i的快照,您正在尽可能地确定参考对象本身。它不再是价值传递,我们有点可以通过引用来回传。

    所以,在记忆中,我们有

    i -> 0x67 -> 0
    i -> 0x67 -> 1
    i -> 0x67 -> 2
    i -> 0x67 -> 3
    i -> 0x67 -> 4
    i -> 0x67 -> 5 (yeah, the loop increments i, then fails the test of i <5. but is still 5)
    

    在你的数组中,你要放一个只能吐出i值的函数。但是你一直在增加我。您放入该阵列的每个功能都将吐出&#34; 5&#34;。

    更进一步......

    function fun1(){
        var result = [];
        for (var i = 0; i < 5; i++){
            result.push( function() {return i++;} );
        }
        return result;
    }
    
    alert(fun1()[0]());
    alert(fun1()[0]());
    alert(fun1()[4]());
    

    在这里,我们正在测试我们是否可以&#34;保持&#34;在初始环境之外的变化。如果您运行此操作,则无法查看存储的任何更改。在第一个例子中,我们增加&#34; i&#34;到6 - 但是当我们再次运行这个例子时,我又是5 - 没有保留增量。

    最后,尝试使用另一个数组函数显示该值也没有增加。

    所以,对你而言,你想要的是以某种方式及时冻结i 进入数组时的价值

    这非常棘手。

    function fun1(){
        var result = [];
        for (var i = 0; i < 5; i++){
            result.push( eval("(function() { return " + i+";})"));
        }
        return result;
    }
    

    在哪里&#34; eval&#34;是一个花哨的javascript函数,它创建代码在运行时运行 - 动态,我的意思是。我用括号括起来,因为

    Why does JavaScript's eval need parentheses to eval JSON data?

    我不知道关于eval的超级数量!

    接下来,其他一个解决方案显示了这一点:

    (function(x){
        result.push( function() {return x} );
    })(i);
    

    放在for循环的上下文中 - 给出

    function fun1(){
        var result = [];
        for (var i = 0; i < 5; i++){
            (function(x){
                result.push( function() {return x} );
            })(i);
        }
        return result;
    }
    

    这样做是为了在其中存在闭包创建一个 new 环境。在这种环境下,&#34; x&#34;永远固定,永远是我当前(即在实例化时)的值。因此,当我们的数组尝试访问给定的闭包时,它存在于x被修复的新环境中。如果我们这样做了:

    function fun1(){
        var result = [];
        for (var i = 0; i < 5; i++){
            (function(){
                result.push( function() {return i;} );
            })();
        }
        return result;
    }
    

    然后我们遇到了同样的问题。 &#34;我&#34; 仍然存在于循环的环境(或范围,如果你愿意)中,所以无论你多少次将其粘贴在函数中,它都会被拾取。

    如果有人读到这篇文章并认为我写的是bollocks,请告诉我!我有点想知道js如何处理内存管理。