Rimozione del listener di eventi che è stato aggiunto con bind


165

In JavaScript, qual è il modo migliore per rimuovere una funzione aggiunta come listener di eventi usando bind ()?

Esempio

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

})();

L'unico modo in cui riesco a pensare è di tenere traccia di ogni ascoltatore aggiunto con bind.

Esempio sopra con questo metodo:

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

})();

Ci sono modi migliori per farlo?


2
Quello che state facendo eccezione this.clickListener = this.clickListener.bind(this);ethis.myButton.addEventListener("click", this.clickListener);
Esailija

È molto carino Questo potrebbe essere un argomento diverso, ma mi sono chiesto se avrei dovuto associare (questo) per il resto dei miei metodi che usano la parola chiave "this" anche se renderebbe inefficienti le chiamate al metodo.
Takfuruya,

Lo faccio sempre come prima cosa nel costruttore per tutti i metodi che verranno passati da qualche parte, indipendentemente dal fatto che li rimuoverò in seguito. Ma non per tutti i metodi, solo quelli che vengono passati in giro.
Esailija,

Quello che stai facendo ha senso. Ma se questo facesse parte di una libreria, ad esempio, non si può mai sapere quali metodi di MyClass (documentati come "pubblici") sarebbero passati in giro.
Takfuruya,

Proprio FYI, la libreria Underscore ha una bindAllfunzione che semplifica i metodi di associazione. All'interno del tuo inizializzatore di oggetti devi solo _.bindAll(this)impostare tutti i metodi nel tuo oggetto su una versione associata. In alternativa, se si desidera solo per legare alcuni metodi (che mi consiglia, per evitare accidentali perdite di memoria), è possibile fornire loro come argomenti: _.bindAll(this, "foo", "bar") // this.baz won't be bound.
machineghost,

Risposte:


274

Sebbene ciò che @machineghost ha detto sia vero, che gli eventi vengono aggiunti e rimossi allo stesso modo, la parte mancante dell'equazione era questa:

Viene creato un nuovo riferimento di funzione dopo che .bind()è stato chiamato!

Vedi Bind () cambia il riferimento della funzione? | Come impostare in modo permanente?

Quindi, per aggiungerlo o rimuoverlo, assegnare il riferimento a una variabile:

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

Questo funziona come previsto per me.


4
Eccellente, questa dovrebbe essere la risposta accettata. Grazie per l'aggiornamento di un vecchio argomento, questo argomento è apparso sul motore di ricerca come hit numero uno e non aveva una soluzione adeguata fino a quando non lo hai pubblicato ora.
Blargh,

Questo non è diverso (e non migliore) dal metodo menzionato nella domanda.
Peter Tseng,

Non riesco a capire, come si fa a farlo funzionare con un evento click, grazie
Alberto Acuña,

@ AlbertoAcuña I browser moderni utilizzano .addEventListener(type, listener)e .removeEventListener(type, listener)per aggiungere e rimuovere eventi su un elemento. Per entrambi, è possibile passare il riferimento alla funzione descritto nella soluzione come listenerparametro, con "click"come tipo. developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
Ben

1
questo mi aiuta anche se questa risposta è stata pubblicata 4 anni fa :)
user2609021

46

Per coloro che hanno questo problema durante la registrazione / rimozione del listener del componente React nel / dal negozio Flux, aggiungi le seguenti righe al costruttore del tuo componente:

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
Bel trucco, ma cosa c'entra React / Flux con qualcosa?
Peter Tseng,

Questo sembra essere l'approccio corretto quando si aggiungono e rimuovono listener di eventi da diverse classi o funzioni prototipo, il che credo sia il collegamento con questo che si applica anche ai componenti / classi di React. Lo stai vincolando a un livello di istanza comune (ad esempio, root).
Keith DC,

1
this.onChange = this.onChange.bind(this)in realtà questo è quello che stavo cercando. La funzione è legata thisper sempre :)
Paweł

2

Non importa se si utilizza o meno una funzione associata; lo rimuovi come qualsiasi altro gestore di eventi. Se il tuo problema è che la versione associata è la sua funzione unica, puoi tenere traccia delle versioni associate o utilizzare ilremoveEventListener firma che non accetta un gestore specifico (anche se ovviamente rimuoverà altri gestori di eventi dello stesso tipo ).

(Come nota a margine, addEventListenernon funziona in tutti i browser; in realtà dovresti usare una libreria come jQuery per eseguire i tuoi collegamenti di eventi in un modo cross-browser per te. Inoltre, jQuery ha il concetto di eventi con spazi dei nomi, che consentono devi associarti a "click.foo"; quando vuoi rimuovere l'evento puoi dire a jQuery "rimuovi tutti gli eventi foo" senza dover conoscere il gestore specifico o rimuovere altri gestori.)


Sono a conoscenza del problema IE. Sto sviluppando un'applicazione che si basa fortemente sulla tela, quindi IE7- è uscito. IE8 supporta canvas ma almeno. IE9 + supporta addEventListener. Gli Eventi con spazio dei nomi di jQuery sembrano molto ordinati. L'unica cosa di cui sono preoccupato è l'efficienza.
Takfuruya,

La gente di jQuery lavora molto duramente per far funzionare bene la propria libreria, quindi non me ne preoccuperei troppo. Tuttavia, dati i severi requisiti del browser, potresti voler dare un'occhiata a Zepto. È un po 'come una versione ridotta di jQuery che è più veloce ma non può supportare i browser più vecchi (e ha alcuni altri limiti).
machineghost,

Gli eventi con spazio dei nomi JQuery sono ampiamente utilizzati e non hanno praticamente alcun problema di prestazioni. Dire a qualcuno di non usare uno strumento che renderà il proprio codice più semplice e (probabilmente più importante) più facile da capire, sarebbe un consiglio orribile, specialmente se fatto per paura irrazionale di JQuery e immaginari problemi di prestazioni.
machineghost,

1
Quale firma sarebbe? La pagina MDN su removeEventListener mostra che sono richiesti entrambi i primi due argomenti.
Coder

Errore mio. Sono passati anni da quando ho scritto quella risposta, ma devo aver pensato a jQuery offo al unbindmetodo. Per rimuovere tutti i listener su un elemento devi tenerne traccia man mano che vengono aggiunti (cosa che jQuery o altre librerie possono fare per te).
machineghost

1

soluzione jQuery:

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

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

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

1

Abbiamo avuto questo problema con una libreria che non potevamo cambiare. Interfaccia utente di Office Fabric, il che significa che non è stato possibile modificare la modalità di aggiunta dei gestori di eventi. Il modo in cui l'abbiamo risolto è stato quello di sovrascrivere addEventListenerilEventTarget prototipo.

Ciò aggiungerà una nuova funzione sugli oggetti element.removeAllEventListers("click")

(post originale: Rimuovi gestore di clic dalla sovrapposizione della finestra di dialogo del tessuto )

        <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

Ecco la soluzione:

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

può usare circa 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

Se vuoi usare "onclick", come suggerito sopra, puoi provare questo:

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

Spero possa essere d'aiuto.


-2

È passato un po 'di tempo ma MDN ha una super spiegazione al riguardo. Questo mi ha aiutato più delle cose qui.

MDN :: EventTarget.addEventListener - Il valore di "this" all'interno del gestore

Offre un'ottima alternativa alla funzione handleEvent.

Questo è un esempio con e senza bind:

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 problema nell'esempio sopra è che non puoi rimuovere l'ascoltatore con bind. Un'altra soluzione sta utilizzando una funzione speciale chiamata handleEvent per rilevare eventuali eventi:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.