循环中的EventListeners [JavaScript]

时间:2013-12-07 17:39:33

标签: javascript javascript-events event-handling addeventlistener

我有几个存储在形状数组中的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-&gt; 2),它也总会给出id-&gt; 1(因为它弹出最后一个)。请解释这种行为以及可能有效的方法。

4 个答案:

答案 0 :(得分:1)

请参阅here

使用underscore.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的当前值,在循环执行之后将是形状数组中的最后一个pieChart对象。

答案 1 :(得分:1)

在内部回调中,使用this而不是再次按id访问元素。

答案 2 :(得分:0)

您可以使用

e.target.id

代替

S.id
事件监听器中的

答案 3 :(得分:0)

考虑到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将具有最后一次迭代。

您需要通过回调工厂(如此related answer)或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 table)。

相关问题