欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

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

程序员文章站 2022-05-23 11:22:36
...
网上搜到的关于该问题的一个方案是借一层函数避免问题
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">


无标题文档