删除通过绑定添加的事件侦听器


164

在JavaScript中,删除使用bind()添加为事件侦听器的函数的最佳方法是什么?

(function(){

    // constructor
    MyClass = function() {
        this.myButton = document.getElementById("myButtonID");
        this.myButton.addEventListener("click", this.clickListener.bind(this));
    };

    MyClass.prototype.clickListener = function(event) {
        console.log(this); // must be MyClass
    };

    // public method
    MyClass.prototype.disableButton = function() {
        this.myButton.removeEventListener("click", ___________);
    };

})();

我能想到的唯一方法是跟踪添加了bind的每个侦听器。

上面的示例使用此方法:

(function(){

    // constructor
    MyClass = function() {
        this.myButton = document.getElementById("myButtonID");
        this.clickListenerBind = this.clickListener.bind(this);
        this.myButton.addEventListener("click", this.clickListenerBind);
    };

    MyClass.prototype.clickListener = function(event) {
        console.log(this); // must be MyClass
    };

    // public method
    MyClass.prototype.disableButton = function() {
        this.myButton.removeEventListener("click", this.clickListenerBind);
    };

})();

有没有更好的方法可以做到这一点?


2
您正在做什么,除了this.clickListener = this.clickListener.bind(this);this.myButton.addEventListener("click", this.clickListener);
Esailija

很好 这可能是一个不同的话题,但是让我想知道我是否应该对其余使用“ this”关键字的方法进行bind(this),即使这会使方法调用效率低下。
takfuruya

对于将要传递到某个地方的所有方法,我始终将其作为构造函数的第一件事,无论以后是否要删除它们。但是,并非所有方法都可以,只是传递的方法。
Esailija

你在做什么很有意义。但是,例如,如果这是库的一部分,则您永远无法知道将传递哪种MyClass的方法(记录为“公共”)。
takfuruya

仅供参考,Underscore库具有bindAll简化绑定方法的功能。在对象初始化程序内部,您只需_.bindAll(this)将对象中的每个方法设置为绑定版本。另外,如果您只想绑定某些方法(为防止意外的内存泄漏,我建议这样做),则可以将它们作为参数提供:_.bindAll(this, "foo", "bar") // this.baz won't be bound
machineghost

Answers:


274

尽管@machineghost所说的是正确的,但事件的添加和删除方式相同,但等式中缺少的部分是:

.bind()调用后会创建一个新的函数引用!

请参见bind()是否会更改函数引用?| 如何永久设置?

因此,要添加或删除它,请将引用分配给变量:

var x = this.myListener.bind(this);
Toolbox.addListener(window, 'scroll', x);
Toolbox.removeListener(window, 'scroll', x);

这对我来说是预期的。


4
太好了,这应该是公认的答案。感谢您更新旧的主题,该主题在搜索引擎上排在第一位,直到您现在发布此主题,它才有合适的解决方案。
Blargh 2014年

这与问题中提到的方法没有不同(也没有更好)。
彼得·曾

我不明白,您如何通过点击事件使其发挥作用,谢谢
AlbertoAcuña16年

@AlbertoAcuña现代浏览器使用.addEventListener(type, listener).removeEventListener(type, listener)添加和删​​除元素上的事件。对于这两种方法,您都可以将解决方案中描述的函数引用作为listener参数"click"以及类型作为参数传递。developer.mozilla.org/en-US/docs/Web/API/EventTarget/...

1
尽管这是4年前发布的答案,但
它对

46

对于那些在将React组件的侦听器注册到Flux存储中或从Flux存储中移除React侦听器时遇到此问题的人,请将以下行添加到您的组件的构造函数中:

class App extends React.Component {
  constructor(props){
    super(props);
    // it's a trick! needed in order to overcome the remove event listener
    this.onChange = this.onChange.bind(this);  
  }
  // then as regular...
  componentDidMount (){
    AppStore.addChangeListener(this.onChange);
  }
  
  componentWillUnmount (){
    AppStore.removeChangeListener(this.onChange);
  }

  onChange () {
    let state = AppStore.getState();
    this.setState(state);
  }
  
  render() {
    // ...
  }
  
}


7
不错的技巧,但是React / Flux与什么有关系?
曾荫权

从不同的类或原型函数添加和删除事件侦听器时,这似乎是正确的方法,我相信与此相关的连接也适用于React组件/类。您正在一个通用(例如,根)实例级别绑定它。
基思DC

1
this.onChange = this.onChange.bind(this)实际上,这就是我想要的。绑定在功能this永远:)
帕维尔

2

是否使用绑定函数都没有关系。您可以像其他事件处理程序一样删除它。如果您的问题是绑定版本是其自己的唯一功能,则可以跟踪绑定版本,或者使用removeEventListener不带特定处理程序的签名(尽管当然会删除相同类型的其他事件处理程序) )。

(请注意,addEventListener并非在所有浏览器中都起作用;您确实应该使用jQuery之类的库为您跨浏览器方式进行事件连接。此外,jQuery具有命名空间事件的概念,您绑定到“ click.foo”;当您想要删除事件时,您可以告诉jQuery“删除所有foo事件”,而不必知道特定的处理程序或删除其他处理程序。)


我知道IE问题。我正在开发一个高度依赖画布的应用程序,因此IE7-不在市场上。IE8支持画布,但至少要支持。IE9 +支持addEventListener。jQuery的命名空间事件看起来非常整洁。我唯一担心的是效率。
takfuruya

jQuery员工非常努力地保持其库的良好性能,因此我不必为此担心太多。但是,鉴于您对浏览器的严格要求,您可能想要查看Zepto。它有点像jQuery的缩小版本,速度更快,但不支持较旧的浏览器(并且有一些其他限制)。
machineghost

jQuery命名空间事件得到了广泛使用,几乎没有性能问题。告诉某人不要使用使他们的代码更容易(并且可能更重要)更易于理解的工具,将是可怕的建议,尤其是这样做是出于对JQuery的非理性恐惧和对性能的虚假考虑。
machineghost

1
那是哪个签名? removeEventListener的MDN页面显示前两个参数都是必需的。
编码器

我的错。自从我写出答案以来已经有好几年了,但是我一定在想着jQuery offunbind方法。要删除元素上的所有侦听器,您必须在添加它们时跟踪它们(这是jQuery或其他库可以为您做的事情)。
machineghost

1

jQuery解决方案:

let object = new ClassName();
let $elem = $('selector');

$elem.on('click', $.proxy(object.method, object));

$elem.off('click', $.proxy(object.method, object));

1

我们无法更改的库存在此问题。Office Fabric UI,这意味着我们无法更改事件处理程序的添加方式。我们解决它的方法是覆盖addEventListenerEventTarget原型。

这将在对象上添加新功能 element.removeAllEventListers("click")

(原始文章:从结构对话框叠加层中删除Click处理程序

        <script>
            (function () {
                "use strict";

                var f = EventTarget.prototype.addEventListener;

                EventTarget.prototype.addEventListener = function (type, fn, capture) {
                    this.f = f;
                    this._eventHandlers = this._eventHandlers || {};
                    this._eventHandlers[type] = this._eventHandlers[type] || [];
                    this._eventHandlers[type].push([fn, capture]);
                    this.f(type, fn, capture);
                }

                EventTarget.prototype.removeAllEventListeners = function (type) {
                    this._eventHandlers = this._eventHandlers || {};
                    if (type in this._eventHandlers) {
                        var eventHandlers = this._eventHandlers[type];
                        for (var i = eventHandlers.length; i--;) {
                            var handler = eventHandlers[i];
                            this.removeEventListener(type, handler[0], handler[1]);
                        }
                    }
                }

                EventTarget.prototype.getAllEventListeners = function (type) {
                    this._eventHandlers = this._eventHandlers || {};
                    this._eventHandlers[type] = this._eventHandlers[type] || [];
                    return this._eventHandlers[type];
                }

            })();
        </script>

0

解决方法如下:

var o = {
  list: [1, 2, 3, 4],
  add: function () {
    var b = document.getElementsByTagName('body')[0];
    b.addEventListener('click', this._onClick());

  },
  remove: function () {
    var b = document.getElementsByTagName('body')[0];
    b.removeEventListener('click', this._onClick());
  },
  _onClick: function () {
    this.clickFn = this.clickFn || this._showLog.bind(this);
    return this.clickFn;
  },
  _showLog: function (e) {
    console.log('click', this.list, e);
  }
};


// Example to test the solution
o.add();

setTimeout(function () {
  console.log('setTimeout');
  o.remove();
}, 5000);

0

可以使用关于ES7:

class App extends React.Component {
  constructor(props){
    super(props);
  }
  componentDidMount (){
    AppStore.addChangeListener(this.onChange);
  }

  componentWillUnmount (){
    AppStore.removeChangeListener(this.onChange);
  }

  onChange = () => {
    let state = AppStore.getState();
    this.setState(state);
  }

  render() {
    // ...
  }

}

-1

如果您要使用“ onclick”(如上所述),则可以尝试以下操作:

(function(){
    var singleton = {};

    singleton = new function() {
        this.myButton = document.getElementById("myButtonID");

        this.myButton.onclick = function() {
            singleton.clickListener();
        };
    }

    singleton.clickListener = function() {
        console.log(this); // I also know who I am
    };

    // public function
    singleton.disableButton = function() {
        this.myButton.onclick = "";
    };
})();

希望对您有所帮助。


-2

已经有一段时间了,但是MDN对此有一个很好的解释。那对我的帮助远胜于这里的东西。

MDN :: EventTarget.addEventListener-处理程序中“ this”的值

它为handleEvent函数提供了很好的选择。

这是带有和不带有绑定的示例:

var Something = function(element) {
  this.name = 'Something Good';
  this.onclick1 = function(event) {
    console.log(this.name); // undefined, as this is the element
  };
  this.onclick2 = function(event) {
    console.log(this.name); // 'Something Good', as this is the binded Something object
  };
  element.addEventListener('click', this.onclick1, false);
  element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}

上面的示例中的问题是您无法使用bind删除侦听器。另一种解决方案是使用一个名为handleEvent的特殊函数来捕获所有事件:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.