通过类名删除所有事件监听器

6
我将使用第一个代码块成功地向一组具有相同类的
标签添加事件监听器,但几乎完全相同的第二个代码块却无法移除它们。有人能解释一下这是为什么吗?
let names = document.querySelectorAll('.playerName');
names.forEach((name) => {
    name.addEventListener('click', () => {selectSelf(name)});
});

2.

function dropEvents() {
    let drops = document.querySelectorAll('.playerName');
    drops.forEach((drop) => {
        drop.removeEventListener('click', () => {selectSelf(name)});
    });
}

他们都选择了具有class为.playerName的相同组<div>标签。在click事件上运行的selectSelf(name)函数只将被点击的<div>的值分配给一个变量,运行一个alert()(这样我就可以知道它成功运行了),然后立即调用dropEvents()函数。因此理论上selectSelf(name)不应该在第一次之后运行,但它确实运行了。
对于.removeEventListener方法,我尝试了许多不同的变化,但没有成功。
编辑:我在w3schools页面上看到了关于这个方法的匿名函数不起作用的信息,这有点改变了我的问题。我尝试了以下内容,但它也没有起作用。
function dropEvents() {
    let drops = document.querySelectorAll('.playerName');
    drops.forEach((drop) => {
        drop.removeEventListener('click', selectSelf);
    });
}

1
箭头函数是匿名的,不能被反向引用,但是removeEventListener()的第二个参数必须是一个函数引用。 - marekful
因为您正在附加函数,但没有保留对它们的引用,所以您唯一能做的就是进行深度克隆并用克隆替换节点。这将删除使用 addEventListener 添加的所有侦听器,但不会删除内联侦听器。 - RobG
为了让 removeEventListener 起作用,你必须传递给它与 addEventListener 中相同的函数,而不仅仅是具有相同代码的函数。 - JLRishe
不要相信w3schools网站,该网站有许多错误,请使用MDN代替。您可以删除匿名函数,只需保留对它们的引用即可。 - RobG
3个回答

6
removeEventListener()的第二个参数必须是一个对被分配为事件侦听器的函数的引用,但你传递了一个新的、文字上声明的箭头函数。
在这个例子中,第二个 () => {selectSelf(name)} 是一个新的箭头函数。已经添加为事件处理程序的那个是一个不同的函数,因此你不能指望它被删除。
要使其工作,保存每个处理程序函数的引用,以便稍后将其传递给removeEventListener()
let names = document.querySelectorAll('.playerName');
const handlers = [];

names.forEach((name) => {
    // Use a unique identifier for each function reference
    handlers[name.id] = () => selectSelf(name);
    
    name.addEventListener('click', handlers[name.id]);
});

function dropEvents() {
    let drops = document.querySelectorAll('.playerName');
    drops.forEach((drop) => {
        drop.removeEventListener('click', handlers[drop.id]);
    });
}

是的,正确的。谢谢指出,我会编辑答案。 - marekful
谢谢!这样做就可以了。 - sethW

2
事件处理程序的签名/引用必须相同。在您的代码中,必须在删除期间引用同一个函数。以下是解决方案。将 click 事件处理程序移至一个函数中。

let names = document.querySelectorAll('.playerName');

const clickEv = () => {
  // selectSelf(name);
  console.log('yay');
};

names.forEach((name) => {
    name.addEventListener('click', clickEv);
});

function dropEvents() {
    let drops = document.querySelectorAll('.playerName');
    drops.forEach((drop) => {
        drop.removeEventListener('click', clickEv);
    });
}
<button type="button" class="playerName">1</button>
<button type="button" class="playerName">2</button>
<button type="button" class="playerName">3</button>
<button type="button" class="playerName">4</button>
<button type="button" class="drop" onClick="dropEvents()">drop</button>


这与 OP 尝试做的不等价,因为 OP 的事件处理程序引用了 name 闭包变量。看起来你只是将其注释掉了,因为你不知道该怎么处理它。 - JLRishe
我正在注释掉这段代码,因为没有提供 selectSelf(name) 函数。问题的所有者可以将其取消注释并尝试。 - Jecfish
那样做不行,因为“name”将没有值。您已经将函数移出了“name”本应该有值的上下文环境。 - JLRishe
跟进人们所说的,这并没有解决需要将“name”传递给“selectSelf(name)”函数的问题。 - sethW

1
为了让removeEventListener起作用,你必须将与addEventListener 相同的函数传递给它,而不仅仅是看起来像的函数。
你尝试做的事情中的一个复杂点是使用闭包变量name,这会创建一种情况,每个单独的处理程序函数都是唯一的匿名函数,这将导致您必须跟踪所使用的所有函数,然后再次引用它们以删除处理程序。
但在这里真的不需要。 name只是对元素本身的引用,可以从事件参数(e.target)中获取。
如果这样做,您可以只使用普通的命名函数,而不是为每个元素创建一个单独的函数并且必须跟踪它们:

let names = document.querySelectorAll('.playerName');

// placeholder
const selectSelf = (el) => console.log(el.textContent);

const clickHandler = (e) => {
  selectSelf(e.target);
};

names.forEach((name) => {
    name.addEventListener('click', clickHandler);
});

function dropEvents() {
    names.forEach((drop) => {
        drop.removeEventListener('click', clickHandler);
    });
}

document.getElementById('drop').addEventListener('click', dropEvents);
<div class="playerName">Glorgon</div>
<div class="playerName">Marlbratt</div>
<div class="playerName">Xaxir</div>
<div class="playerName">Splengraf</div>
<button type="button" id="drop">Drop Events</button>


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