addEventListener在NodeList上的应用

41

NodeList支持addEventListener吗?如果不支持,那么向NodeList的所有节点添加EventListener的最佳方法是什么?目前我正在使用如下代码片段,是否有更好的方法?

var ar_coins = document.getElementsByClassName('coins');
for(var xx=0;xx < ar_coins.length;xx++)
{
        ar_coins.item(xx).addEventListener('dragstart',handleDragStart,false);
}

3
虽然我知道jQuery不是万能的解决方案,但它确实使得这些问题变得无关紧要:$('.coins')。on('dragstart',handleDragStart); - zzzzBov
8个回答

48

没有办法在不遍历每个元素的情况下完成它。当然,你可以编写一个函数来为你完成。

function addEventListenerList(list, event, fn) {
    for (var i = 0, len = list.length; i < len; i++) {
        list[i].addEventListener(event, fn, false);
    }
}

var ar_coins = document.getElementsByClassName('coins');
addEventListenerList(ar_coins, 'dragstart', handleDragStart); 

或者一个更专业化的版本:

function addEventListenerByClass(className, event, fn) {
    var list = document.getElementsByClassName(className);
    for (var i = 0, len = list.length; i < len; i++) {
        list[i].addEventListener(event, fn, false);
    }
}

addEventListenerByClass('coins', 'dragstart', handleDragStart); 

虽然您没有询问jQuery,但这是jQuery特别擅长的内容:

$('.coins').on('dragstart', handleDragStart);

请注意,getElementsByClassName 返回一个实时节点列表。 - salmatron
3
@SalmanPK - 这里不应该有影响。getElementsByClassName()的结果立即被使用并且没有被存储,因此在使用它时它没有机会发生改变。 - jfriend00

27

我能想到的最好的解决方案是这个:

const $coins = document.querySelectorAll('.coins')
$coins.forEach($coin => $coin.addEventListener('dragstart', handleDragStart));

请注意,这使用了ES6功能,因此请确保首先进行转译!


美味的... :-) - Marco Faustinelli
2
超级干净 - 而且两年后,随着更多浏览器支持ES6,不需要进行太多的转译。 - Velojet
谢谢@okay56k,我更新了答案以反映这一点。 - Kris Selbekk

15

实际上有一种方法可以在没有循环的情况下完成这个操作:

[].forEach.call(nodeList,function(e){e.addEventListener('click',callback,false)})

这种方法在我的一个单行帮助库中使用 - nanoQuery


7
这不使用循环控制结构和奇怪的变量。 - Multiversum
9
即使你认为这仍然是一个循环,这还是非常牛逼的。谢谢。 - skribbz14
在我的使用情况下,相对于传统的for循环,这种方法只能节省5个按键。然而,我喜欢不必定义任何迭代变量...嘿,节省5个按键仍然是一件好事。 - Andrew Willems
17
也许这可以省去您打几个按键,但会让下一个开发者花费额外的几分钟来理解。净损失。 - Kabir Sarin
3
FYI,现在是2018年,大多数浏览器中的NodeList都是可迭代的。nodeList.foreach(el => el.addEventListener('click', callback)) 对于大多数人来说应该已经足够了。如果不行,ES6允许我们将类似数组的对象“展开”成一个实际的数组。因此,像这样:[...nodeList].forEach 也应该能够工作。 - romellem
显示剩余3条评论

7
最简单的例子是将此功能添加到NodeList
NodeList.prototype.addEventListener = function (event_name, callback, useCapture)
{
    for (var i = 0; i < this.length; i++)
    {
      this[i].addEventListener(event_name, callback, useCapture);
    }
};

现在您可以做到以下事情:
document.querySelectorAll(".my-button").addEventListener("click", function ()
{
    alert("Hi");
});

同样地,您可以使用forEach循环。

NodeList.prototype.forEach = function (callback)
{
    for (var i = 0; i < this.length; i++)
    {
      callback(this[i], i);
    }
};

使用:

document.querySelectorAll(".buttons").forEach(function (element, id)
{
    input.addEventListener("change", function ()
    {
        alert("button: " + id);
    });
});

编辑:请注意,自2016年11月以来,NodeList.prototype.forEach已经存在于FF中。但是不支持IE。


6
在 ES6 中,您可以使用 Array.from 从 NodeList 创建数组,例如:
ar_coins = document.getElementsByClassName('coins');
Array
 .from(ar_coins)
 .forEach(addEvent)

function addEvent(element) {
  element.addEventListener('click', callback)
}

或者使用箭头函数。
Array
  .from(ar_coins)
  .forEach(element => element.addEventListener('click', callback))

你的箭头函数需要在最后一个位置加上一个闭括号才能使其正常工作。干杯。 - Brian Zelip

3
另一种解决方案是使用事件委托。 只需使用 addEventListener 添加到最近的父元素上,该元素包含 .coins 元素,并在回调函数中使用 event.target 来检查单击是否确实发生在类为 "coins" 的元素上。

惯用模式:theParent.addEventListener("click", ({ target }) => { const element = target.closest(".coins"); if(element){ doTheThing(element); } });. - Sebastian Simon

2
我想另一种选择是使用Object.definePropertyNodeList上定义addEventListener。这样,您可以像对待单个节点一样处理NodeList。
例如,我在这里创建了一个jsfiddle:http://jsfiddle.net/2LQbe/ 关键点在于:
Object.defineProperty(NodeList.prototype, "addEventListener", {
    value: function (event, callback, useCapture) {
        useCapture = ( !! useCapture) | false;
        for (var i = 0; i < this.length; ++i) {
            if (this[i] instanceof Node) {
                this[i].addEventListener(event, callback, useCapture);
            }
        }
        return this;
    }
});

我给这个答案点赞是因为:(1)它回答了问题,(2)与其他答案不同,(3)很聪明,(4)可能正是某人在寻找的。然而,我必须说,任何修改核心JavaScript对象原型的东西都让我感到紧张。 - Andrew Willems
@AndrewWillems 谢谢!我理解修改本地原型的一般关注点,但我认为这类似于从你不拥有的库中扩展一个类。 :) - Duncan

0

你也可以使用原型

NodeList.prototype.addEventListener = function (type, callback) {
    this.forEach(function (node) {
        node.addEventListener(type, callback);
    });
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接