Удаление прослушивателя событий, который был добавлен с помощью bind

В 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);
    };

})();

Есть ли лучшие способы сделать это?

11 ответов

Решение

Хотя то, что сказал @machineghost, было правдой, события добавляются и удаляются одинаково, но отсутствующей частью уравнения было следующее:

Новая ссылка на функцию создается после .bind() называется!

См. Bind() изменяет ссылку на функцию? | Как установить постоянно?

Итак, чтобы добавить или удалить его, присвойте ссылку на переменную:

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

Это работает, как и ожидалось для меня.

Для тех, у кого есть эта проблема при регистрации / удалении слушателя компонента React в / из хранилища Flux, добавьте строки ниже в конструктор вашего компонента:

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() {
    // ...
  }
  
}

Неважно, используете ли вы связанную функцию или нет; вы удаляете его так же, как и любой другой обработчик событий. Если ваша проблема заключается в том, что связанная версия представляет собой собственную уникальную функцию, вы можете либо отслеживать связанные версии, либо использовать removeEventListener подпись, которая не требует определенного обработчика (хотя, конечно, она удалит другие обработчики событий того же типа).

(Как примечание, addEventListener не работает во всех браузерах; Вы действительно должны использовать библиотеку, такую ​​как jQuery, чтобы проводить кросс-браузерные подключения к вашим событиям. Кроме того, в jQuery есть концепция пространств имен, которые позволяют вам связываться с "click.foo"; когда вы хотите удалить событие, вы можете сказать jQuery "удалить все события foo" без необходимости знать конкретный обработчик или удалять другие обработчики.)

У нас возникла проблема с библиотекой, которую мы не могли изменить. Пользовательский интерфейс Office Fabric, что означало, что мы не могли изменить способ добавления обработчиков событий. Мы решили эту проблему, перезаписавaddEventListener на EventTarget опытный образец.

Это добавит новую функцию для объектов element.removeAllEventListers("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>

Решение jQuery:

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

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

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

Как говорили другие, создает новый экземпляр функции, и поэтому прослушиватель событий не может быть удален, если он не записан каким-либо образом.

Для более красивого стиля кода вы можете сделать метод функцией ленивого геттера, чтобы он автоматически заменялся связанной версией при первом доступе:

      class MyClass {
  activate() {
    window.addEventListener('click', this.onClick);
  }

  deactivate() {
    window.removeEventListener('click', this.onClick);
  }

  get onClick() {
    const func = (event) => {
      console.log('click', event, this);
    };
    Object.defineProperty(this, 'onClick', {value: func});
    return func;
  }
}

Если функция стрелки ES6 не поддерживается, используйте вместо.

Подход Райхмана Сергея тоже хорош, особенно для занятий. Преимущество этого подхода в том, что он более самозаверняющийся и не имеет отдельного кода где-либо еще. Это также работает для объекта, у которого нет конструктора или инициатора.

Вот решение:

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);

Сейчас 2016 год, и стандарт DOM мало помог в решении довольно распространенной проблемы, с которой мы сталкиваемся время от времени.

Да, единственный способ удалить обработчик ограниченных событий - сохранить ссылку на ограниченную функцию и использовать ее в removeEventListener, как указано в других решениях здесь.

Однако, когда у вас много слушателей, становится грязно. Никто не придумал простых функций, которые абстрагируют от необходимости хранить ссылки на ограниченные функции. Я придумал две функции с именами on() и off() (имена, вдохновленные jQuery), которые я добавил ко всем элементам HTMLE, добавив их в прототип. Код здесь. (Работает только в IE 11+, поскольку использует WeakMap)

Таким образом, используя это, вы можете добавить прослушиватели событий и удалить их следующим образом:

this.myButton.on('click', this.clickListener, this);
this.myButton.off('click', this.clickListener, this); //yup, it's removed

Детали реализации и принятые решения многочисленны, поэтому я не буду это объяснять:)

Если вам не нравится добавлять функции к собственным объектам, то вы можете добиться этого, немного отредактировав мой код. (А если серьезно, стандарт DOM должен был добавить некоторый API, чтобы решить эту проблему для нас в первую очередь).

Если вы хотите использовать "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 = "";
    };
})();

Я надеюсь, что это помогает.

Можно использовать о 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() {
    // ...
  }

}

Это было некоторое время, но у 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 для перехвата любых событий:

Другие вопросы по тегам