Suppression de l'écouteur d'événements qui a été ajouté avec bind

165

En JavaScript, quel est le meilleur moyen de supprimer une fonction ajoutée en tant qu'écouteur d'événements à l'aide de bind ()?

Exemple

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

})();

La seule façon dont je peux penser est de garder une trace de chaque auditeur ajouté avec bind.

Exemple ci-dessus avec cette méthode:

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

})();

Existe-t-il de meilleures façons de procéder?

takfuruya
la source
2
Ce que vous faites sauf this.clickListener = this.clickListener.bind(this);etthis.myButton.addEventListener("click", this.clickListener);
Esailija
C'est très bien. C'est peut-être un sujet différent, mais cela m'a amené à me demander si je devrais faire bind (this) pour le reste de mes méthodes qui utilisent le mot-clé "this" même si cela rendrait les appels de méthode inefficaces.
takfuruya
Je fais toujours cela en premier lieu dans le constructeur pour toutes les méthodes qui vont être passées quelque part, que je les supprime plus tard. Mais pas pour toutes les méthodes, juste celles qui sont transmises.
Esailija
Ce que vous faites a du sens. Mais si cela faisait partie d'une bibliothèque, par exemple, vous ne pouvez jamais savoir quelles méthodes MyClass (documentées comme étant "publiques") seraient transmises.
takfuruya
Juste pour info, la bibliothèque Underscore a une bindAllfonction qui simplifie les méthodes de liaison. Dans votre initialiseur d'objet, il vous suffit _.bindAll(this)de définir chaque méthode de votre objet sur une version liée. Sinon, si vous voulez seulement lier certaines méthodes (que je vous recommande, pour éviter les fuites de mémoire accidentelle), vous pouvez leur fournir comme arguments: _.bindAll(this, "foo", "bar") // this.baz won't be bound.
machineghost

Réponses:

274

Bien que ce que @machineghost ait dit soit vrai, que les événements sont ajoutés et supprimés de la même manière, la partie manquante de l'équation était la suivante:

Une nouvelle référence de fonction est créée après avoir .bind()été appelée!

Voir Est - ce que bind () change la référence de la fonction? | Comment définir définitivement?

Donc, pour l'ajouter ou le supprimer, assignez la référence à une variable:

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

Cela fonctionne comme prévu pour moi.

Ben
la source
4
Excellent, cela devrait être la réponse acceptée. Merci d'avoir mis à jour un ancien sujet, ce sujet est apparu sur les moteurs de recherche en tant que numéro un et il n'avait pas de solution appropriée jusqu'à ce que vous publiiez ceci maintenant.
Blargh
Ce n'est pas différent de (et pas meilleur que) la méthode mentionnée dans la question.
Peter Tseng
Je ne comprends pas, comment le faire fonctionner avec un événement de clic, merci
Alberto Acuña
@ AlbertoAcuña Les navigateurs modernes utilisent .addEventListener(type, listener)et .removeEventListener(type, listener)pour ajouter et supprimer des événements sur un élément. Pour les deux, vous pouvez transmettre la référence de fonction décrite dans la solution comme listenerparamètre, avec "click"comme type. developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
Ben
1
cela m'aide même si cette réponse postée il y a 4 ans :)
user2609021
46

Pour ceux qui rencontrent ce problème lors de l'enregistrement / de la suppression de l'auditeur du composant React dans / du magasin Flux, ajoutez les lignes ci-dessous au constructeur de votre composant:

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
la source
7
Belle astuce, mais qu'est-ce que React / Flux a à voir avec quoi que ce soit?
Peter Tseng
Cela semble être la bonne approche lors de l'ajout et de la suppression d'écouteurs d'événements de différentes classes ou fonctions prototypes, ce qui, je crois, est que le lien avec cela s'applique également aux composants / classes React. Vous le liez à un niveau d'instance commun (par exemple, racine).
Keith DC
1
this.onChange = this.onChange.bind(this)en fait c'est ce que je cherchais. La fonction liée thispour toujours :)
Paweł
2

Peu importe que vous utilisiez une fonction liée ou non; vous le supprimez de la même manière que tout autre gestionnaire d'événements. Si votre problème est que la version liée est sa propre fonction unique, vous pouvez soit garder une trace des versions liées, soit utiliser la removeEventListenersignature qui ne prend pas de gestionnaire spécifique (bien que cela supprimera bien sûr d'autres gestionnaires d'événements du même type ).

(En remarque, addEventListenercela ne fonctionne pas dans tous les navigateurs; vous devriez vraiment utiliser une bibliothèque comme jQuery pour faire vos connexions d'événements de manière inter-navigateurs pour vous. De plus, jQuery a le concept d'événements à espaces de noms, qui permettent vous devez vous lier à "click.foo"; lorsque vous voulez supprimer l'événement, vous pouvez dire à jQuery "supprimer tous les événements foo" sans avoir à connaître le gestionnaire spécifique ou à supprimer d'autres gestionnaires.)

machineghost
la source
Je suis conscient du problème d'IE. Je développe une application qui s'appuie fortement sur le canevas, donc IE7- sont sortis. IE8 prend en charge le canevas mais au minimum. IE9 + prend en charge addEventListener. Les événements Namespaced de jQuery semblent très soignés. La seule chose qui m'inquiète, c'est l'efficacité.
takfuruya
Les gens de jQuery travaillent très dur pour que leur bibliothèque fonctionne bien, donc je ne m'inquiéterais pas trop à ce sujet. Cependant, étant donné les exigences strictes de votre navigateur, vous voudrez peut-être consulter Zepto à la place. C'est un peu comme une version réduite de jQuery qui est plus rapide mais ne peut pas prendre en charge les navigateurs plus anciens (et a d'autres limites).
machineghost
Les événements d'espacement de noms JQuery sont largement utilisés et n'ont pratiquement aucun problème de performances. Dire à quelqu'un de ne pas utiliser un outil qui rendra son code plus facile et (sans doute plus important) plus facile à comprendre serait un conseil horrible, surtout si cela est fait par peur irrationnelle de JQuery et des problèmes de performances imaginaires.
machineghost
1
De quelle signature s'agit-il? La page MDN sur removeEventListener montre que les deux premiers arguments sont requis.
Coderer
Mon erreur. Cela fait des années que j'ai écrit cette réponse, mais j'ai dû penser à jQuery offou à la unbindméthode. Pour supprimer tous les écouteurs d'un élément, vous devez les suivre à mesure qu'ils sont ajoutés (ce que jQuery ou d'autres bibliothèques peuvent faire pour vous).
machineghost
1

Solution jQuery:

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

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

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

Nous avons eu ce problème avec une bibliothèque que nous ne pouvions pas changer. Interface utilisateur d'Office Fabric, ce qui signifiait que nous ne pouvions pas modifier la façon dont les gestionnaires d'événements étaient ajoutés. La façon dont nous l'avons résolu a consisté à écraser le addEventListenersur le EventTargetprototype.

Cela ajoutera une nouvelle fonction sur les objets element.removeAllEventListers("click")

(article d'origine: Supprimer le gestionnaire de clic de la superposition de la boîte de dialogue de tissu )

        <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
la source
0

Voici la solution:

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
la source
0

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

}
chiique
la source
-1

Si vous souhaitez utiliser 'onclick', comme suggéré ci-dessus, vous pouvez essayer ceci:

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

J'espère que cela aide.

Diogo Schneider
la source
-2

Cela fait longtemps mais MDN a une super explication à ce sujet. Cela m'a aidé plus que les trucs ici.

MDN :: EventTarget.addEventListener - La valeur de "this" dans le gestionnaire

Il offre une excellente alternative à la fonction handleEvent.

Voici un exemple avec et sans liaison:

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
}

Un problème dans l'exemple ci-dessus est que vous ne pouvez pas supprimer l'écouteur avec bind. Une autre solution consiste à utiliser une fonction spéciale appelée handleEvent pour intercepter tous les événements:

Noitidart
la source