为什么在函数调用中捕获对象的值?

Bri*_*don 5 javascript

此代码应该在您单击时弹出带有图像编号的警报:

for(var i=0; i<10; i++) {
    $("#img" + i).click(
        function () { alert(i); }
    );
}
Run Code Online (Sandbox Code Playgroud)

您可以在http://jsfiddle.net/upFaJ/看到它无效.我知道这是因为所有的click-handler闭包都指向同一个对象i,因此每个处理程序在触发时会弹出"10".

但是,当我这样做时,它工作正常:

for(var i=0; i<10; i++) {
    (function (i2) {
        $("#img" + i2).click(
            function () { alert(i2); }
        );
    })(i);
}
Run Code Online (Sandbox Code Playgroud)

你可以在http://jsfiddle.net/v4sSD/看到它的工作原理.

它为什么有效?i内存中只有一个对象,对吧?对象总是通过引用传递,而不是复制,因此自执行函数调用应该没有区别.两个代码片段的输出应该相同.那么为什么i要复制对象10次呢?它为什么有效?

我认为这个版本不起作用很有意思:

for(var i=0; i<10; i++) {
    (function () {
        $("#img" + i).click(
            function () { alert(i); }
        );
    })();
}
Run Code Online (Sandbox Code Playgroud)

似乎将对象作为函数参数传递会产生重大影响.


编辑:好的,所以前面的例子可以解释i为通过值传递给函数调用的primitives().但是这个使用真实对象的例子呢?

for(var i=0; i<5; i++) {
    var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
    toggler.click(function () { toggler.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
    $("#container").append(toggler);
}
Run Code Online (Sandbox Code Playgroud)

不工作:http://jsfiddle.net/Zpwku/

for(var i=0; i<5; i++) {
    var toggler = $("<img/>", { "src": "http://www.famfamfam.com/lab/icons/silk/icons/cross.png" });
    (function (t) {
        t.click(function () { t.attr("src", "http://www.famfamfam.com/lab/icons/silk/icons/tick.png"); });
        $("#container").append(t);
    })(toggler);
}
Run Code Online (Sandbox Code Playgroud)

工作:http://jsfiddle.net/YLSn6/

sle*_*man 7

大多数答案都是正确的,因为将对象作为函数参数传递会破坏闭包,从而允许我们在循环内为函数赋值.但是我想指出为什么会出现这种情况,这不仅仅是闭包的特例.

你看,javascript将参数传递给函数的方式与其他语言有点不同.首先,它似乎有两种方法可以根据天气来实现,它是一个原始值或一个对象.对于原始值,它似乎通过值传递,对于对象,它似乎通过引用传递.

javascript如何传递函数参数

实际上,javascript所做的真正解释只使用一种机制解释了这两种情况,以及它为什么打破了闭包.

javascript实际上是通过引用副本传递参数.也就是说,它创建了对参数的另一个引用,并将该新引用传递给函数.

通过价值?

假设javascript中的所有变量都是引用.在其他语言中,当我们说变量是一个引用时,我们希望它的行为如下:

var i = 1;
function increment (n) { n = n+1 };
increment(i); // we would expect i to be 2 if i is a reference
Run Code Online (Sandbox Code Playgroud)

但在javascript中,情况并非如此:

console.log(i); // i is still 1
Run Code Online (Sandbox Code Playgroud)

这是一个经典的价值传递不是吗?

通过参考传递?

但等等,对于对象而言,这是一个不同的故事:

var o = {a:1,b:2}
function foo (x) {
    x.c = 3;
}
foo(o);
Run Code Online (Sandbox Code Playgroud)

如果参数是按值传递的,我们希望o对象不变,但是:

console.log(o); // outputs {a:1,b:2,c:3}
Run Code Online (Sandbox Code Playgroud)

那是经典的传递.因此,根据我们传递原始类型或对象的天气,我们有两种行为.

等等,什么?

但等一下,看看这个:

var o = {a:1,b:2,c:3}
function bar (x) {
    x = {a:2,b:4,c:6}
}
bar(o);
Run Code Online (Sandbox Code Playgroud)

现在看看会发生什么:

console.log(o); // outputs {a:1,b:2,c:3}
Run Code Online (Sandbox Code Playgroud)

什么!这不是通过参考传递!价值不变!

这就是我称之为参考副本的原因.如果我们这样思考,一切都是有道理的.传递给函数时,我们不需要将基元视为具有特殊行为,因为对象的行为方式相同.如果我们尝试修改变量指向的对象然后它就像传递引用一样工作,但如果我们尝试修改引用本身,那么它就像pass by value一样工作.

这也解释了为什么通过将变量作为函数参数传递来打破闭包.因为函数调用将创建另一个不受闭包约束的引用,就像原始变量一样.

结语:我骗了

在我们结束之前还有一件事.之前我说过,它统一了原始类型和对象的行为.实际上没有,原始类型仍然不同:

var i = 1;
function bat (n) { n.hello = 'world' };
bat(i);
console.log(i.hello); // undefined, i is unchanged
Run Code Online (Sandbox Code Playgroud)

我放弃.对此没有任何意义.这就是它的方式.

  • 只是添加到deceze说:当你将一个原语作为一个Object处理(在它上面调用一个方法,添加一个属性等)时,会在它周围创建一个新的*temporary*对象,并在它被使用后立即丢弃.这就是为什么即使没有这个功能,结尾的代码也不会起作用. (2认同)