我们都知道javascript中的对象类型是传引用地址的而不是传值的,我今天遇到个不明白的问题就是当我把代码在点击事件写的时候令我以外的传值了,而我写到循环里面却是传的地址代码如下
这里是普通循环触发,结果是2s后三次alert,每次都是2,这是和预期的一样last给bb传递了引用地址,就是当last改变时候bb也改变了
//普通循环demo for(i=0;i<3;i++){ var aa = {name:"aa"}; aa.name = i; last = aa; var bb = last; setTimeout(function() { alert(bb.name); }, 2000); }
下面是事件触发 就是在2s内迅速点击鼠标3次,结果2s后三次alert依次是0,1,2。我很不解,为什么这里last传给bb的不是引用地址呢??bb为什么不改变呢?很奇怪望讨论
//事件demo var i = 0; var last = null; document.addEventListener("click", function(event) { var aa = {name:"aa"}; aa.name = i; last = aa; var bb = last; setTimeout(function() { alert(bb.name); }, 2000); i++; });
我来尝试解释这个现象。
先说办法,就是把这句
[code="js"] alert(bb.name); [/code]
都改成
[code="js"]alert(bb.name + " - " + last.name); [/code]
再执行,你会看到
- 循环那例子连续alert了3次“2 - 2”,
- 事件那例子分别alert了“0 - 2”,“1 - 2”,“2 - 2”。
我认为原因是,[b][color=green]每次事件处理都有自己独立的context,其中保存了局部变量,比如bb。setTimeout中的function也是在这个context下执行的。[/color][/b]
- 循环例子中只有一个默认的context,循环结束后bb被改成2,所以三次都是2。
- 事件的例子中先后又有了3个独立的context,每个context里面的局部变量bb分别是0,1,2。但全局变量last还都是被改成了2。所以结果如上。
我又把第二个例子改成这样
[code="html"]
//事件demo var i = 0; var last = null; document.addEventListener("click", function(event) { var aa = {name:"aa"}; aa.name = i; last = aa; var bb = last; var x = (i==0)?2000:1000; setTimeout(function() { alert(bb.name + " - " + last.name); }, x); i++; });
[/code]
也就是让第二次点击的timeout先到期,得到的结果是“1 - 1”,“0 - 1”这样的顺序。似乎也能验证上面的论点。
所以,这个恐怕不是值传递还是引用传递的问题,而是执行context混合作用域的问题。
最近又有看过些书籍,正式的描述应该是:“JS的作用域是由函数决定的。”
用这中说法——应该是正式的说法——来讲,
例子1:
bb是全局的,也就是window的。所以每次alert都是最终被改变的值。
例子2:
bb是function(event)这个函数的,这个函数先后被调用了三次。每次有各自的值0,1,2,其实也是当时的last这个全局变量的值。
意思差不多,不知道是否更容易理解些呢?
例子1如果写成下面这样,就会和例子2一样了。
[code="html"]
//普通循环demo for (i = 0; i < 3; i++) { var aa = { name : "aa" }; aa.name = i; last = aa; var bb = last; (function(cc) { setTimeout(function() { alert(cc.name); }, 2000); })(bb); }
[/code]
也就是套个匿名函数,这样就可以留住每次的bb了。