繁体   English   中英

无论如何定义回调,如何删除事件侦听器

[英]How can I remove an event listener no matter how the callback is defined

多年来,我遇到了尝试在JavaScript中删除事件监听器的问题。 通常,我不得不创建一个独立的函数作为处理程序。 但这只是草率的,特别是在添加了箭头功能的情况下,这只是一种痛苦。

我不寻求ONCE解决方案。 无论如何定义回调,这都需要在所有情况下都有效。 这必须是原始JS,以便任何人都可以使用它。

由于函数clickHandler是一个唯一函数,因此以下代码可以正常工作,并且可以被addEventListenerremoveEventListener

此示例已更新,以显示我过去遇到的问题

 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 ,您需要将传递给addEventListenerEXACT 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
 });

有一个使用闭包的简单解决方案。

通过将代码同时移至addEventListenerremoveEventListener到一个函数中,可以轻松完成任务:

 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM