Ereignis-Listener entfernen, der mit bind hinzugefügt wurde

164

Was ist in JavaScript der beste Weg, um eine als Ereignis-Listener hinzugefügte Funktion mit bind () zu entfernen?

Beispiel

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

})();

Der einzige Weg, den ich mir vorstellen kann, besteht darin, jeden mit bind hinzugefügten Listener im Auge zu behalten.

Oberes Beispiel mit dieser Methode:

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

})();

Gibt es bessere Möglichkeiten, dies zu tun?

Takfuruya
quelle
2
Was Sie tun, außer this.clickListener = this.clickListener.bind(this);undthis.myButton.addEventListener("click", this.clickListener);
Esailija
Das ist sehr schön. Dies mag ein anderes Thema sein, aber ich habe mich gefragt, ob ich für den Rest meiner Methoden, die das Schlüsselwort "this" verwenden, binden soll, obwohl dies Methodenaufrufe ineffizient machen würde.
Takfuruya
Ich mache dies immer als erstes im Konstruktor für alle Methoden, die irgendwo übergeben werden, unabhängig davon, ob ich sie später entfernen werde. Aber nicht für alle Methoden, nur für die, die herumgereicht werden.
Esailija
Was Sie tun, macht Sinn. Wenn dies beispielsweise Teil einer Bibliothek wäre, können Sie nie wissen, welche MyClass-Methoden (dokumentiert als "öffentlich") weitergegeben werden.
Takfuruya
Nur zu Ihrer Information, die Unterstreichungsbibliothek verfügt über eine bindAllFunktion, die die Bindungsmethoden vereinfacht. In Ihrem Objektinitialisierer müssen Sie lediglich _.bindAll(this)jede Methode in Ihrem Objekt auf eine gebundene Version setzen. Wenn Sie nur einige Methoden binden möchten (was ich empfehlen würde, um versehentliche Speicherverluste zu vermeiden), können Sie sie alternativ als Argumente angeben : _.bindAll(this, "foo", "bar") // this.baz won't be bound.
Machineghost

Antworten:

274

Obwohl das, was @machineghost sagte, wahr ist, dass Ereignisse auf dieselbe Weise hinzugefügt und entfernt werden, war der fehlende Teil der Gleichung folgender:

Nach dem .bind()Aufruf wird eine neue Funktionsreferenz erstellt !

Siehe Ändert bind () die Funktionsreferenz? | Wie dauerhaft einstellen?

Um es hinzuzufügen oder zu entfernen, weisen Sie den Verweis einer Variablen zu:

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

Das funktioniert bei mir wie erwartet.

Ben
quelle
4
Ausgezeichnet, dies sollte die akzeptierte Antwort sein. Vielen Dank, dass Sie ein altes Thema aktualisiert haben. Dieses Thema wurde in der Suchmaschine als Nummer-1-Treffer angezeigt und hatte keine richtige Lösung, bis Sie dies jetzt gepostet haben.
Blargh
Dies unterscheidet sich nicht von der in der Frage genannten Methode (und ist auch nicht besser als diese).
Peter Tseng
Ich kann nicht verstehen, wie du es mit einem Klick-Event zum
Laufen
@ AlbertoAcuña Moderne Browser verwenden .addEventListener(type, listener)und .removeEventListener(type, listener)fügen Ereignisse zu einem Element hinzu und entfernen sie. Für beide können Sie die in der Lösung beschriebene Funktionsreferenz als listenerParameter und "click"als Typ übergeben. developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
Ben
1
Dies hilft mir, obwohl diese Antwort vor 4 Jahren gepostet wurde :)
user2609021
46

Fügen Sie für diejenigen, die dieses Problem beim Registrieren / Entfernen des Listeners der React-Komponente zum / vom Flux-Speicher haben, die folgenden Zeilen zum Konstruktor Ihrer Komponente hinzu:

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

Raichman Sergey
quelle
7
Netter Trick, aber was hat React / Flux mit irgendetwas zu tun?
Peter Tseng
Dies scheint der richtige Ansatz zu sein, wenn Ereignis-Listener zu verschiedenen Klassen oder Prototypenfunktionen hinzugefügt und daraus entfernt werden. Ich glaube, dass der Zusammenhang damit auch für React-Komponenten / Klassen gilt. Sie binden es auf einer allgemeinen (z. B. Root-) Instanzebene.
Keith DC
1
this.onChange = this.onChange.bind(this)eigentlich war es das, wonach ich gesucht habe. Die Funktion thisfür immer gebunden :)
Paweł
2

Es spielt keine Rolle, ob Sie eine gebundene Funktion verwenden oder nicht. Sie entfernen es wie jeden anderen Ereignishandler. Wenn Ihr Problem darin besteht, dass die gebundene Version eine eigene eindeutige Funktion ist, können Sie entweder die gebundenen Versionen verfolgen oder die removeEventListenerSignatur verwenden, für die kein bestimmter Handler erforderlich ist (obwohl dadurch natürlich andere Ereignishandler desselben Typs entfernt werden ).

(Nebenbei bemerkt, addEventListenerfunktioniert nicht in allen Browsern. Sie sollten wirklich eine Bibliothek wie jQuery verwenden, um Ihre Ereignisverbindungen für Sie browserübergreifend durchzuführen. Außerdem verfügt jQuery über das Konzept von Ereignissen mit Namespace, die dies ermöglichen Sie müssen an "click.foo" binden. Wenn Sie das Ereignis entfernen möchten, können Sie jQuery mitteilen, dass "alle foo-Ereignisse entfernen", ohne den jeweiligen Handler kennen oder andere Handler entfernen zu müssen.)

Maschinenhost
quelle
Ich bin mir des IE-Problems bewusst. Ich entwickle eine Anwendung, die stark auf Canvas basiert, sodass IE7- nicht verfügbar sind. IE8 unterstützt Canvas, jedoch mindestens. IE9 + unterstützt addEventListener. jQuerys Namespaced Events sehen sehr ordentlich aus. Das einzige, worüber ich mir Sorgen mache, ist die Effizienz.
Takfuruya
Die jQuery-Leute arbeiten sehr hart daran, dass ihre Bibliothek gut funktioniert , deshalb würde ich mir darüber keine Sorgen machen. Angesichts Ihrer strengen Browseranforderungen möchten Sie jedoch möglicherweise stattdessen Zepto ausprobieren. Es ist wie eine verkleinerte Version von jQuery, die schneller ist, aber ältere Browser nicht unterstützt (und einige andere Einschränkungen hat).
Machineghost
JQuery-Ereignisse mit Namespace sind weit verbreitet und haben praktisch keine Leistungsprobleme. Jemandem zu sagen, er solle kein Tool verwenden, das seinen Code einfacher und (wohl noch wichtiger) verständlicher macht, wäre ein schrecklicher Rat, insbesondere wenn dies aus irrationaler Angst vor JQuery und imaginären Leistungsproblemen geschieht.
Machineghost
1
Welche Unterschrift wäre das? Die MDN-Seite auf removeEventListener zeigt, dass beide der ersten beiden Argumente erforderlich sind.
Coderer
Mein Fehler. Es ist Jahre her, seit ich diese Antwort geschrieben habe, aber ich muss an jQuerys offoder unbindMethode gedacht haben . Um alle Listener eines Elements zu entfernen, müssen Sie sie verfolgen, wenn sie hinzugefügt werden (was jQuery oder andere Bibliotheken für Sie tun können).
Machineghost
1

jQuery-Lösung:

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

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

$elem.off('click', $.proxy(object.method, object));
Ed Kolosovsky
quelle
1

Wir hatten dieses Problem mit einer Bibliothek, die wir nicht ändern konnten. Office Fabric-Benutzeroberfläche, was bedeutete, dass wir die Art und Weise, wie Ereignishandler hinzugefügt wurden, nicht ändern konnten. Die Art und Weise, wie wir es gelöst haben, bestand darin, addEventListenerden EventTargetPrototyp zu überschreiben .

Dadurch wird eine neue Funktion für Objekte hinzugefügt element.removeAllEventListers("click")

(Originalbeitrag: Click-Handler aus Stoffdialog-Overlay entfernen )

        <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>
Peter
quelle
0

Hier ist die Lösung:

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);
Nazar Vynnytskyi
quelle
0

kann über ES7 verwenden:

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
quelle
-1

Wenn Sie 'onclick' wie oben vorgeschlagen verwenden möchten, können Sie Folgendes versuchen:

(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 = "";
    };
})();

Ich hoffe, es hilft.

Diogo Schneider
quelle
-2

Es ist schon eine Weile her, aber MDN hat eine super Erklärung dafür. Das hat mir mehr geholfen als das Zeug hier.

MDN :: EventTarget.addEventListener - Der Wert von "this" im Handler

Es bietet eine großartige Alternative zur handleEvent-Funktion.

Dies ist ein Beispiel mit und ohne Bindung:

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
}

Ein Problem im obigen Beispiel ist, dass Sie den Listener nicht mit bind entfernen können. Eine andere Lösung ist die Verwendung einer speziellen Funktion namens handleEvent, um Ereignisse abzufangen:

Noitidart
quelle