Удаление прослушивателя событий, который был добавлен с привязкой

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

})();

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

Ответ 1

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

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

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

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

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

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

Ответ 2

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

class App extends React.Component {
  constructor(props){
    super(props);
    // it 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() {
    // ...
  }
  
}

Ответ 3

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

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

Ответ 4

Решение jQuery:

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

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

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

Ответ 5

<rant> It 2016 и стандарт DOM не помогли в решении довольно распространенной проблемы, с которой мы сталкиваемся время от времени. </rant>

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

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

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

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

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

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

Ответ 6

Вот решение:

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

Ответ 7

У нас была эта проблема с библиотекой, которую мы не могли изменить. Пользовательский интерфейс 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>

Ответ 8

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

Надеюсь, это поможет.

Ответ 9

Было немного, но 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
}

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