此代码应该在您单击时弹出带有图像编号的警报:
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)
大多数答案都是正确的,因为将对象作为函数参数传递会破坏闭包,从而允许我们在循环内为函数赋值.但是我想指出为什么会出现这种情况,这不仅仅是闭包的特例.
你看,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)
我放弃.对此没有任何意义.这就是它的方式.