Видалення слухача подій, доданого з прив’язкою


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", ___________);
    };

})();

Єдиний спосіб, про який я можу придумати, - це відслідковувати кожного слухача, доданого з прив’язкою.

Наведений вище приклад із цим методом:

(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);
Есаїлія

Це дуже приємно. Це може бути інша тема, але це змусило мене замислитися, чи варто робити прив'язку (це) для решти моїх методів, які використовують ключове слово "це", хоча це зробить виклики методів неефективними.
takfuruya

Я завжди роблю це як перше в конструкторі для всіх методів, які будуть кудись передані, незалежно від того, чи збираюся я їх потім видалити. Але не для всіх методів, лише тих, які передаються навколо.
Есаїлія

Те, що ви робите, має сенс. Але, якби це, наприклад, була частиною бібліотеки, ви ніколи не знаєте, які методи MyClass (документально підтверджені як "загальнодоступні") будуть передані.
takfuruya

Тільки FYI, бібліотека підкреслення має 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. Ви зв'язуєте його на загальному (наприклад, root) рівні екземпляра.
Кіт DC

1
this.onChange = this.onChange.bind(this)насправді це те, що я шукав. Функція поширюється thisназавжди :)
Paweł

2

Не має значення, використовуєте ви зв’язану функцію чи ні; ви видалите його так само, як і будь-який інший обробник подій. Якщо проблема полягає в тому, що зв'язана версія є власною унікальною функцією, ви можете або відслідковувати зв'язані версії, або використовувати removeEventListenerпідпис, який не бере конкретного обробника (хоча, звичайно, це видалить інші обробники подій того ж типу ).

(Як бічна примітка, addEventListenerце працює не у всіх браузерах; ви дійсно повинні використовувати бібліотеку, як jQuery, щоб робити свої підключення до подій крос-браузерним способом. Також, jQuery має концепцію подій у просторі імен, які дозволяють вам потрібно прив’язати до "click.foo"; коли ви хочете видалити подію, ви можете сказати jQuery "видалити всі події foo", не знаючи конкретного обробника чи видалення інших обробників.)


Мені відомо про випуск IE. Я розробляю програму, яка значною мірою покладається на полотно, тому IE7 - не в роботі. IE8 підтримує полотно, але як мінімум. IE9 + підтримує addEventListener. Імена JQuery в Іменах виглядають дуже акуратно. Єдине, що мене хвилює - це ефективність.
takfuruya

Люди jQuery дуже працюють над тим, щоб їхня бібліотека працювала добре, тому я б не переживав про це занадто. Однак, враховуючи ваші суворі вимоги до браузера, ви можете замість цього перевірити Zepto. Це щось на зразок зменшеної версії jQuery, яка є швидшою, але не може підтримувати старі браузери (і має деякі інші обмеження).
machineghost

Події в просторі імен JQuery широко використовуються і практично не мають проблем з роботою. Говорити комусь не використовувати інструмент, який полегшить їхній код і (може бути, що важливіше) легше зрозуміти, було б жахливою порадою, особливо якщо це зроблено з ірраціонального страху перед JQuery та уявних проблем з продуктивністю.
machineghost

1
Який це був би підпис? Сторінка MDN на deleteEventListener показує, що потрібні обидва перші два аргументи.
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() {
    // ...
  }

}

-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 - значення "цього" в обробнику

Це дає чудову альтернативу функції 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 для лову будь-яких подій:

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.