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

165

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

})();

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

takfuruya
источник
2
Что вы делаете , кроме this.clickListener = this.clickListener.bind(this);иthis.myButton.addEventListener("click", this.clickListener);
Esailija
Это очень мило. Это может быть другая тема, но это заставило меня задуматься, должен ли я делать связывание (this) для остальных моих методов, использующих ключевое слово "this", даже если это сделает вызовы методов неэффективными.
Такфуруя
Я всегда делаю это первым делом в конструкторе для всех методов, которые будут где-то переданы, независимо от того, собираюсь ли я удалить их позже. Но не для всех методов, только для тех, которые распространяются.
Esailija
То, что вы делаете, имеет смысл. Но если бы это было частью библиотеки, например, вы никогда не узнаете, какие методы MyClass (задокументированные как «публичные») будут переданы.
Такфуруя
Просто FYI, библиотека Underscore имеет bindAllфункцию, которая упрощает методы привязки. Внутри вашего инициализатора объекта вы просто _.bindAll(this)устанавливаете для каждого метода в вашем объекте привязанную версию. С другой стороны , если вы только хотите , чтобы связать некоторые методы (которые я рекомендовал бы, чтобы предотвратить случайные утечки памяти), вы можете предоставить их в качестве аргументов: _.bindAll(this, "foo", "bar") // this.baz won't be bound.
Machineghost

Ответы:

274

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

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

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

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

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

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

Бен
источник
4
Отлично, это должен быть принятый ответ. Спасибо за обновление старой темы, эта тема попала в поисковую систему как хит номер один, и у нее не было правильного решения, пока вы не разместили ее сейчас.
Blargh
Это ничем не отличается (и не лучше) от метода, упомянутого в вопросе.
Питер Ценг
Я не могу понять, как вы можете заставить его работать с событием щелчка, спасибо
Альберто Акунья,
@ AlbertoAcuña Современные браузеры используют .addEventListener(type, listener)и .removeEventListener(type, listener)для добавления и удаления событий в элементе. Для обоих вы можете передать ссылку на функцию, описанную в решении, как listenerпараметр, "click"а в качестве типа. developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
Бен
1
это мне даже помогает - хотя этот ответ был опубликован 4 года назад :)
user2609021
46

Для тех, у кого есть эта проблема при регистрации / удалении слушателя компонента 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() {
    // ...
  }
  
}

Райхман Сергей
источник
7
Хороший трюк, но при чем тут React / Flux?
Питер Ценг
Это кажется правильным подходом при добавлении и удалении прослушивателей событий из разных классов или функций-прототипов, что, как я полагаю, связано с этим и в отношении компонентов / классов React. Вы связываете это на общем (например, корневом) уровне экземпляра.
Кит
1
this.onChange = this.onChange.bind(this)на самом деле это то, что я искал. Функция thisвсегда на связи :)
Павел
2

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

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

machineghost
источник
Я знаю о проблеме IE. Я разрабатываю приложение, которое в значительной степени опирается на Canvas, поэтому IE7- вышли. IE8 поддерживает canvas, но как минимум. IE9 + поддерживает addEventListener. События пространства имен jQuery выглядят очень аккуратно. Единственное, что меня беспокоит - это эффективность.
Такфуруя
Ребята из jQuery очень усердно работают, чтобы их библиотека работала хорошо, так что я бы не стал сильно беспокоиться об этом. Однако, учитывая ваши строгие требования к браузеру, вы можете попробовать Zepto. Это похоже на уменьшенную версию jQuery, которая работает быстрее, но не поддерживает старые браузеры (и имеет некоторые другие ограничения).
Machineghost
События пространства имен JQuery широко используются и практически не имеют проблем с производительностью. Советовать кому-либо не использовать инструмент, который сделает его код более простым и (возможно, более важным) понятным, было бы ужасным советом, особенно если это было сделано из-за иррационального страха JQuery и мнимых проблем производительности.
Machineghost
1
Какая подпись это будет? Страница MDN на removeEventListener показывает, что оба из первых двух аргументов являются обязательными.
Coderer
Моя ошибка. Прошло много лет с тех пор, как я написал этот ответ, но я, должно быть, думал о jQuery offили unbindметоде. Чтобы удалить всех слушателей в элементе, вы должны отслеживать их по мере их добавления (что может сделать 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, что означало, что мы не могли изменить способ добавления обработчиков событий. То, как мы решили это, было переписать 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>
Питер
источник
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() {
    // ...
  }

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

Noitidart
источник