[英]How can I remove an event listener no matter how the callback is defined
多年来,我遇到了尝试在JavaScript中删除事件监听器的问题。 通常,我不得不创建一个独立的函数作为处理程序。 但这只是草率的,特别是在添加了箭头功能的情况下,这只是一种痛苦。
我不寻求ONCE解决方案。 无论如何定义回调,这都需要在所有情况下都有效。 这必须是原始JS,以便任何人都可以使用它。
由于函数clickHandler
是一个唯一函数,因此以下代码可以正常工作,并且可以被addEventListener
和removeEventListener
:
此示例已更新,以显示我过去遇到的问题
const btnTest = document.getElementById('test'); let rel = null; function clickHandler() { console.info('Clicked on test'); } function add() { if (rel === null) { rel = btnTest.addEventListener('click', clickHandler); } } function remove() { btnTest.removeEventListener('click', clickHandler); } [...document.querySelectorAll('[cmd]')].forEach( el => { const cmd = el.getAttribute('cmd'); if (typeof window[cmd] === 'function') { el.addEventListener('click', window[cmd]); } } );
<button cmd="add">Add</button> <button cmd="remove">Remove</button> <button id="test">Test</button>
您以前可以使用arguments.callee
做到这一点:
var el = document.querySelector('#myButton'); el.addEventListener('click', function () { console.log('clicked'); el.removeEventListener('click', arguments.callee); //<-- will not work });
<button id="myButton">Click</button>
但是使用箭头功能不起作用:
var el = document.querySelector('#myButton'); el.addEventListener('click', () => { console.log('clicked'); el.removeEventListener('click', arguments.callee); //<-- will not work });
<button id="myButton">Click</button>
有没有更好的办法??
更新
如@Jonas Wilms所说,这种方式可以工作:
var el = document.querySelector('#myButton'); el.addEventListener('click', function handler() { console.log('clicked'); el.removeEventListener('click', handler); //<-- will work });
<button id="myButton">Click</button>
除非您需要使用绑定:
var obj = { setup() { var el = document.querySelector('#myButton'); el.addEventListener('click', (function handler() { console.log('clicked', Object.keys(this)); el.removeEventListener('click', handler); //<-- will work }).bind(this)); } } obj.setup();
<button id="myButton">Click</button>
问题在于,有太多方法可以为addEventListener
函数提供事件处理程序,并且如果您在函数中传递的方式在重构中发生更改,则代码可能会中断。
您不能直接使用箭头函数或任何匿名函数,并且期望能够删除侦听器。
要删除侦听removeEventListener
,您需要将传递给addEventListener
的EXACT SAME ARGUMENTS传递给removeEventListener
,但是当您使用匿名函数或箭头函数时,您无权访问该函数,因此无法将其传递给removeEventListener
作品
const anonFunc = () => { console.log("hello"); }
someElem.addEventListener('click', anonFunc);
someElem.removeEventListener('click', anonFunc); // same arguments
不起作用
someElem.addEventListener('click', () => { console.log("hello"); });
someElem.removeEventListener('click', ???) // you don't have a reference
// to the anon function so you
// can't pass the correct arguments
// to remove the listener
您的选择是
一个例子是@Intervalia闭包。 他跟踪您传入的函数和其他参数,并返回可以使用remove侦听器使用的函数。
我经常使用的一种最适合我需要的解决方案是跟踪所有侦听器并将其全部删除的类。 它不是返回闭包,而是返回一个id,但它也仅允许删除所有侦听器,这些侦听器在我现在构建某些东西并希望稍后将其拆除时对我有用。
function ListenerManager() { let listeners = {}; let nextId = 1; // Returns an id for the listener. This is easier IMO than // the normal remove listener which requires the same arguments as addListener this.on = (elem, ...args) => { (elem.addEventListener || elem.on || elem.addListener).call(elem, ...args); const id = nextId++; listeners[id] = { elem: elem, args: args, }; if (args.length < 2) { throw new Error('too few args'); } return id; }; this.remove = (id) => { const listener = listeners[id]; if (listener) { delete listener[id]; const elem = listener.elem; (elem.removeEventListener || elem.removeListener).call(elem, ...listener.args); } }; this.removeAll = () => { const old = listeners; listeners = {}; Object.keys(old).forEach((id) => { const listener = old[id]; if (listener.args < 2) { throw new Error('too few args'); } const elem = listener.elem; (elem.removeEventListener || elem.removeListener).call(elem, ...listener.args); }); }; }
用法就像
const lm = new ListenerManager();
lm.on(saveElem, 'click', handleSave);
lm.on(newElem, 'click', handleNew);
lm.on(plusElem, 'ciick', handlePlusOne);
const id = lm.on(rangeElem, 'input', handleRangeChange);
lm.remove(id); // remove the input event on rangeElem
lm.removeAll(); // remove events on all elements managed by this ListenerManager
请注意,上面的代码是ES6,必须进行更改以支持真正的旧浏览器,但是想法是相同的。
只需使用一个命名函数表达式:
var el = document.querySelector('#myButton');
el.addEventListener('click', function handler() {
console.log('clicked');
el.removeEventListener('click', handler); //<-- will work
});
确保可以将其包装在函数中:
function once(selector, evt, callback) {
var el = document.querySelector(selector);
el.addEventListener(evt, function handler() {
callback();
el.removeEventListener(evt, handler); //<-- will work
});
}
once("#myButton", "clicl", () => {
// do stuff
});
有一个使用闭包的简单解决方案。
通过将代码同时移至addEventListener
和removeEventListener
到一个函数中,可以轻松完成任务:
function ael(el, evt, cb, options) { console.log('Adding', evt, 'event listener for', el.outerHTML); el.addEventListener(evt, cb, options); return function() { console.log('Removing', evt, 'event listener for', el.outerHTML); el.removeEventListener(evt, cb, options); } } const btnTest = document.getElementById('test'); let rel = null; function add() { if (rel === null) { rel = ael(btnTest, 'click', () => { console.info('Clicked on test'); }); } } function remove() { if (typeof rel === 'function') { rel(); rel = null; } } function removeAll() { rels.forEach(rel => rel()); } const rels = [...document.querySelectorAll('[cmd]')].reduce( (rels, el) => { const cmd = el.getAttribute('cmd'); if (typeof window[cmd] === 'function') { rels.push(ael(el, 'click', window[cmd])); } return rels; }, [] );
<button cmd="add">Add</button> <button cmd="remove">Remove</button> <button id="test">Test</button> <hr/> <button cmd="removeAll">Remove All</button>
上面的函数ael
允许元素,事件类型和回调都保存在函数的关闭范围内。 当您调用ael
它将调用addEventListener
,然后返回一个将调用removeEventListener
的函数。 稍后在代码中,您将调用该返回的函数,它将成功删除事件侦听器,而不必担心如何创建回调函数。
这是es6版本:
const ael6 = (el, evt, cb, options) => (el.addEventListener(evt, cb, options), () => el.removeEventListener(evt, cb, options));
您可以使用EventTarget.addEventListener()
的once
选项 :
注意:除IE外,所有浏览器均支持。
var el = document.querySelector('#myButton'); el.addEventListener('click', () => { console.log('clicked'); }, { once: true });
<button id="myButton">Click</button>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.