我在shapes数组中存储了一些pieChart对象。。。我想为每个对象添加事件侦听器。我试着这样做:
var tempS = shapes.slice();
for(var i=0; i<shapes.length; i++)
{
var S = tempS.pop();
if(S.name == 'pieChart')
{
document.getElementById(S.id).addEventListener('mousedown', function(e){
alert(S.id);
}, false);
}
}
这里的问题是,即使当我点击pieChart2(id->2)时,它也总是会给出id->1(最后弹出)。请解释这种行为以及可能的有效方法。
请参阅此处
使用undercore.js:
_.each(shapes, function(s) {
if(s.name === 'pieChart') {
document.getElementById(s.id).addEventListener('mousedown',function(e) {
e = e || window.event;
var target = e.target || e.srcElement;
alert(target.id);
});
}
});
为了解释代码的行为,您必须记住javascript使用函数范围而不是块范围。因此,由于您在eventListener回调函数之外定义了S,因此回调中对S的引用将是S的当前值,在循环执行之后,S将是shapes数组中的最后一个pieChart对象。
在回调中,使用this
,而不是通过id
再次访问元素。
考虑到var
声明具有函数作用域(如果在全局作用域中声明,则为全局),您的代码等效于:
var tempS = shapes.slice(),
i, S;
for (i = 0; i < shapes.length; i++) {
S = tempS.pop();
if (S.name == 'pieChart') {
document.getElementById(S.id).addEventListener('mousedown', function (e) {
alert(S.id);
}, false);
}
}
现在,很明显,S
的作用域在for
块之外,并且由于事件处理程序将在循环结束后触发,S
将具有上一次迭代的值。
您需要通过回调工厂(如此相关答案所示)或IIFE创建一个新的作用域,以捕获迭代的当前S
值。
var tempS = shapes.slice();
for (var i = 0; i < shapes.length; i++) { (function() {//introduces a new scope
var S = tempS.pop(); //now S is scoped inside this iteration's IIFE
if (S.name == 'pieChart') {
document.getElementById(S.id).addEventListener('mousedown', function (e) {
alert(S.id); //will reference the IIFE scope's S
}, false);
}
}()); }
您也可以用许多替代方法来实现这一点,例如,使用Array#forEach:
var tempS = shapes.slice();
shapes.forEach(function(S) { //S is now scoped in the forEach callback scope
if (S.name == 'pieChart') {
document.getElementById(S.id).addEventListener('mousedown', function (e) {
alert(S.id);
}, false);
}
});
注意,这将改变迭代顺序,但我相信这对这个用例来说并不重要。
在ES6中,可以使用let
创建块范围的变量:
for (var i = 0; i < shapes.length; i++) {
let S = tempS.pop(); //scoped inside the `for` block
if (S.name == 'pieChart') {
document.getElementById(S.id).addEventListener('mousedown', function (e) {
alert(S.id);
}, false);
}
}
我将把这种方法留给未来的读者,因为let
还没有准备好生产。不过,大多数最新的浏览器都有实验性的实现(请参阅ES6-compat表)。
您可以使用
e.target.id
而是
S.id
事件中侦听器