无论如何定义回调,如何删除事件侦听器 [英] How can I remove an event listener no matter how the callback is defined

查看:104
本文介绍了无论如何定义回调,如何删除事件侦听器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

For years I ran into problems trying to remove an event listener in JavaScript. Often I would have to create an independent function as the handler. But that is just sloppy and, especially with the addition of arrow functions, just a pain.

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

由于函数clickHandler是唯一的函数,并且可以同时用于addEventListenerremoveEventListener,因此以下代码可以正常工作:

The following code works fine since the function clickHandler is a unique function and can be used by both addEventListener and removeEventListener:

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

This example has been updated to show what I have run into in the past

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来做到这一点:

You used to be able to do it with 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>

但是使用箭头功能不起作用:

But using an arrow function does not work:

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所说,这种方式将起作用:

As stated by @Jonas Wilms this way will work:

 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函数提供事件处理程序,并且如果您传递函数的方式在重构中发生变化,则代码可能会中断.

The problem is that there are too many ways to provide an event handler to the addEventListener function and your code might break if the way you pass in the function changes in a refactor.

推荐答案

您可以直接使用箭头功能或任何匿名功能,并希望能够删除侦听器.

You can NOT use an arrow function or any anonymous function directly and expect to be able to remove the listener.

要删除侦听器,您需要在传递给addEventListener的同时将 EXACT SAME ARGUMENTS 传递给removeEventListener,但是当您使用匿名函数或箭头函数时,您无权访问功能,因此您不可能将其传递到removeEventListener

To remove a listener requires you pass the EXACT SAME ARGUMENTS to removeEventListener as you passed to addEventListener but when you use an anonymous function or an arrow function you do not have access to that function so it's impossible for you to pass it into 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闭包.他跟踪您传入的函数和其他参数,并返回一个可以使用删除侦听器的函数.

One example is @Intervalia closure. He tracks the function and other arguments you passed in and returns a function you can use the remove the listener.

我经常使用的一种通常可以满足我需要的解决方案是跟踪所有侦听器并将其全部删除的类.代替闭包,它返回一个id,但它也只允许删除所有侦听器,这些侦听器在我现在构建某些东西并希望稍后将其拆除时对我有用.

One solution I often use which often fits my needs is a class that tracks all the listeners and remove them all. Instead of a closure it returns an id but it also allows just removing all listeners which I find useful when I build up something now and want to tear it down something later

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,必须对其进行更改以支持真正的旧浏览器,但思路是相同的.

note the code above is ES6 and would have to be changed to support really old browsers but the ideas are the same.

这篇关于无论如何定义回调,如何删除事件侦听器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆