首页  >  文章  >  后端开发  >  JavaScript 循环添加事件时闭包的影响有哪些解法?

JavaScript 循环添加事件时闭包的影响有哪些解法?

WBOY
WBOY原创
2016-06-06 16:23:101319浏览

网上搜到的关于该问题的一个方案是借一层函数避免问题
blog.csdn.net/victorn/a
不过到底还是很难理解.. 还有其他的方法去理解和解决吗?
更新: 我草草套了一层函数还好也避开了

回复内容:

很高兴有一个纯JS的问题。
1,@杨咖啡 说的JS传参是传值不传址,其实不是这样的。JS中传参有两种方式:by value and by sharing.
像C,C++,Java,他们传参方式是by value 和 by reference。前者就是传值,后者是传址。而JS也是这样的,前者是传值,后者是传址。
By value是对于原始数据类型,例如int,char之类的;而By sharing 和By reference是对于高级数据结构,如Object,struct之类。我们可以想象到一个Object或是struct 不能仅仅通过传值进行传参。
一个简单的例子说明by reference和 by sharing的不同。
var bar;
var foo = bar;
bar = {'key' : 'value'};
console.log(foo , bar );
By sharing 中foo 是undefined , bar 是{'key' : 'value'}; 而By reference 则应该两者都是{'key' : 'value'}。

2. 其实LZ要理解这个问题,要明白JS中的作用域(scope)。
每个函数在创建完成时,他有3个重要的内置属性(property)也同时被创建。
{
AO //记录function内的变量,参数等信息
this // 就是在调用this.xx的时候的this
scope // 指向外层函数AO的一个链(在实现的时候,可能通过数组来实现).
}
JS中,大家经常讲的Scope其实是这样:SCOPE=AO+scope.
回到闭包的问题上:
如果我们这样写这个程序:
for(var i =0; i link[i].onclick = function(){ alert(i); }; // inner function
}
可以得到inner function的SCOPE是这样的:

{
AO
this // 等于link[i]
scope // 指向window的记录,包括我们需要的变量i
}
这个for循环会立即执行完毕,那么当onclick触发时,inner function查找变量 i 时,会在AO+scope中找,AO中没有,scope中的变量i已经成为了link.length.

利用大家所说的闭包写这个程序:
//here is the window scope
for(var i =0; i

link[i].onclick = (function(i){ // outer function
return function(){ //inner function
alert(i);
};
})(i);

}
分析inner function的SCOPE:
{
AO // no important infomation
this // we don't care it.
scope //outer function and window scope
}
outer function的SCOPE
{
AO // 包含参数i
this // don't care it .
scope // window scope.
}


这时,如果inner function被触发,他会从自己的AO以及scope(outer function的AO 和 window scope)中找寻变量i. 可以看到outer function的AO中已经包含了i,而且对于这个for循环,会有对应有N个(function(){})() 被创建执行。所以每个inner function都有一个特定的包含了变量 i 的outer function。

这样就可以顺利输出0,1,2,3。。。。。。。。。

结论: 我们可以看到,闭包其实就是因为Scope产生的,所以,广义上来讲,所有函数都是闭包。


另外,这里面也包含了,this, function expression 和function declaration的区别,这里就不一一讲了。

3. 另外一种方法:
利用 dom onclick事件的bubble特性,也就是@xiiiiiin所讲的弄个代理。

在link dom节点的父节点上定义onclick事件监听。参数为e(其他的名字也可以,但要有参数)。 这样我们通过e.target就可以知道是那个子节点被click了,也可以做相应的处理。
这是一个比较好的方法。(闭包有时会产生内存泄漏)。

大概就说这么多吧,还要上班呢。希望对LZ有用。如果哪里错了,也请多多批评指正。 我觉得最好的方式就是通过包装一层函数来解决。

将原来的
alink.onclick = function(){alert(i)};
改成:
(function(i) { alink.onclick = function(){alert(i)}; })(i);

我觉得这是最好的方法了,js 中只有 function 才会划分作用域(和 python 有点像),if/else、for 循环都不会划分作用域,所以原来的方式六次循环引用的都是同一个变量 i,由于闭包绑定到 function 中去。
现在包装了一层之后,i 被传递到内层的匿名函数 local 作用域中去,所以六次循环都会建立独立的 i (因为是六个不同的作用域)。 不看那文章你的问题还真难理解。
你把参数进去嘛:
alink.onclick = (function(i){
return function(){
alert(i);
};
})(i);


另外我不喜欢 onxxx 属性。




PS: 知乎用富文本编辑器弄得我贴代码都麻烦,另外,答案的后部分第 N 次消失看不到了。

这跟JS函数的传参方式和事件的赋值方式有关。
1、JS函数传参是传值不传址的。
2、onclick的值应该给一个函数声明,事件触发时只会传一个event参数给声明的函数。

如果在循环中使用alink[i].onclick = function() { alert(i); };
i 不是这个匿名函数的参数,是传址进去的,当onclick事件触发的时候循环已经结束了,i 已经是最后一个值了。
如果声明 function(i) { alert(i); }
那这个 i 就指代了 event,这时候事件触发的时候只会弹出触发的事件名。

而使用alink[i].onclick = (function(_i) { return function() { alert(_i); } })(i);
这里是执行外层的匿名函数返回内层的这个匿名函数传给onclick。
这里注意外层函数是立即执行的,带一个参数,是我们传给它的,而不是事件触发器
内层函数是不带参数的,事件执行时触发器会传给它一个event值。
对循环中的每一个 i 都会生成一个匿名函数,i 作为生成的匿名函数的参数,是传值的。
相当于循环中当 i = 2 的时候,生成了这样一个函数:function() { alert(2); }; 赋值给了alink[2].onclick,即 alink[2].onclick = function() { alert(2); };
这才是我们想要的。

PS:闭包只是个手法,而不是解决问题的核心所在。
这种手法跟下面的方法是等价的,而下面并没有用闭包。
var al = function(param) {
return function() {
alert(param);
}
}
循环中alink[i].onlick = al(i); 呃……这例子,更偏向于变量作用域的问题吧 重新思考了一下这个问题,它的关键在于弄清JavaScript变量作用域和作用域链,前面的回答似乎都没有解释清楚。

《JavaScript: The Good Parts》中提到了这个问题,看这个例子:
// BAD EXAMPLE
var addActions = function (nodes) {
for (var i = 0; i nodes[i].onclick = function (e) {
alert(i);
};
}
};
// END BAD EXAMPLE

解释是这样的:
函数的本意是给每个事件处理器不同的 i 。但它未能达到目的,因为事件处理器函数绑定了 i 本身,而不是函数在构造时的变量 i 的值。


之所以是这样的,看过《JavaScript: The Definitive Guide》就会明白:
所有的JavaScript函数都是闭包:它们都是对象,都关联到作用域链。每次调用JavaScript函数的时候,都会为之创建一个新的对象来保存局部变量,并把这个对象添加至作用域链中(简单理解:作用域链就是对象列表)。
这个糟糕的例子中function (e) { alert(i); }都会在同一个函数调用中定义,因此它们共享变量 i 。也就是说它们的作用域链里面的 i 都是同一个 i 。即关联到闭包的作用域链都是“活动的”。

《JavaScript: The Good Parts》当然也给出了解决方案:
var addActions = function (nodes) {
var helper = function (i) {
return function (e) {
alert(i);
};
};

for (var i = 0; i nodes[i].onclick = helper(i);
}
};

解释: 在循环之外创建一个辅助函数,而不是在循环中创建函数。 看一下这篇文章就全部都清楚了 JavaScript的执行上下文 在这里我觉得排名第一的回答太过于晦涩,一般人根本看不懂。
在这里我提出一个比较有意思的猜想,
//————————————————————————————————————————
//贴出代码
w3.org/TR/xhtml1/DTD/xh">
w3.org/1999/xhtml">


无标题文档










<script><br>var oUl1=document.getElementById('ul1');<br>//alert(oUl1);<br>var aLi=document.getElementsByTagName('li');<br>var length=aLi.length;<br>for(var i=0;i<length;i++)<br>{<br><br> aLi[i].onclick=(function (index){<br> var y=i;<br> return function (){<br><br> // alert(index);<br> alert(y);<br> }; <br> }<br> )(i);<br>}<br>//提出猜想,引用次数加一变成2不再有跨作用域问题<br><br>//猜测正确<br></script>

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn