Usuwanie detektora zdarzeń, który został dodany za pomocą bind

164

Jaki jest najlepszy sposób na usunięcie funkcji dodanej jako detektor zdarzeń za pomocą bind () w JavaScript?

Przykład

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

})();

Jedynym sposobem, jaki przychodzi mi do głowy, jest śledzenie każdego słuchacza dodanego za pomocą bind.

Powyższy przykład z tą metodą:

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

})();

Czy są na to lepsze sposoby?

takfuruya
źródło
2
Co robisz oprócz this.clickListener = this.clickListener.bind(this);ithis.myButton.addEventListener("click", this.clickListener);
Esailija,
To bardzo miłe. Może to być inny temat, ale zastanawiałem się, czy powinienem zrobić bind (this) dla pozostałych moich metod, które używają słowa kluczowego „this”, nawet jeśli spowodowałoby to nieefektywność wywołań metod.
takfuruya,
Zawsze robię to jako pierwszą czynność w konstruktorze dla wszystkich metod, które mają zostać gdzieś przekazane, niezależnie od tego, czy zamierzam je później usunąć. Ale nie dla wszystkich metod, tylko tych, które są przekazywane.
Esailija,
To, co robisz, ma sens. Ale jeśli na przykład byłaby to część biblioteki, nigdy nie wiadomo, które metody MyClass (udokumentowane jako „publiczne”) zostaną przekazane.
takfuruya
Dla Twojej wiadomości, biblioteka Underscore ma bindAllfunkcję, która upraszcza metody wiązania. Wewnątrz inicjalizatora obiektu po prostu _.bindAll(this)ustawiasz każdą metodę w obiekcie na powiązaną wersję. Ewentualnie, jeśli tylko chcą wiązać pewne metody (co polecam, aby zapobiec przypadkowemu przecieki pamięci), można podać je jako argumenty _.bindAll(this, "foo", "bar") // this.baz won't be bound.
machineghost

Odpowiedzi:

274

Chociaż to, co powiedział @machineghost, było prawdą, zdarzenia są dodawane i usuwane w ten sam sposób, brakująca część równania wyglądała tak:

Po wywołaniu zostanie utworzone nowe odwołanie do funkcji .bind()!

Zobacz Czy bind () zmienia odwołanie do funkcji? | Jak ustawić na stałe?

Aby więc ją dodać lub usunąć, przypisz odwołanie do zmiennej:

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

To działa zgodnie z oczekiwaniami dla mnie.

Ben
źródło
4
Doskonale, to powinna być akceptowana odpowiedź. Dzięki za aktualizację starego tematu, ten temat pojawił się w wyszukiwarce jako numer jeden i nie miał odpowiedniego rozwiązania, dopóki nie opublikowałeś tego teraz.
Blargh
Nie różni się to od (i nie lepiej) od metody wspomnianej w pytaniu.
Peter Tseng,
Nie rozumiem, jak sprawić, by to działało za pomocą zdarzenia kliknięcia, dzięki
Alberto Acuña
@ AlbertoAcuña Nowoczesne przeglądarki używają .addEventListener(type, listener)i .removeEventListener(type, listener)do dodawania i usuwania zdarzeń w elemencie. W obu przypadkach odwołanie do funkcji opisane w rozwiązaniu można przekazać jako listenerparametr, z "click"typem. developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
Ben
1
to mi pomaga, mimo że ta odpowiedź została opublikowana 4 lata temu :)
user2609021
46

Dla tych, którzy mają ten problem podczas rejestracji / usuwania nasłuchiwania komponentu React do / ze sklepu Flux, dodaj poniższe linie do konstruktora komponentu:

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
źródło
7
Niezła sztuczka, ale co ma z tym wspólnego React / Flux?
Peter Tseng
Wydaje się, że jest to właściwe podejście przy dodawaniu i usuwaniu detektorów zdarzeń z różnych klas lub funkcji prototypowych, co, jak sądzę, ma zastosowanie również do komponentów / klas React. Wiążesz go na wspólnym (np. Głównym) poziomie wystąpienia.
Keith DC
1
this.onChange = this.onChange.bind(this)właściwie to jest to, czego szukałem. Funkcja związana na thiszawsze :)
Paweł
2

Nie ma znaczenia, czy używasz funkcji związanej, czy nie; usuwasz go w taki sam sposób, jak każdy inny program obsługi zdarzeń. Jeśli Twoim problemem jest to, że powiązana wersja jest własną unikalną funkcją, możesz albo śledzić powiązane wersje, albo użyć removeEventListenerpodpisu, który nie przyjmuje określonej procedury obsługi (chociaż oczywiście spowoduje to usunięcie innych programów obsługi zdarzeń tego samego typu ).

(Na marginesie, addEventListenernie działa we wszystkich przeglądarkach; naprawdę powinieneś używać biblioteki, takiej jak jQuery, do podłączania zdarzeń w sposób między przeglądarkami. Ponadto jQuery ma koncepcję zdarzeń w przestrzeni nazw, która umożliwia możesz powiązać się z „click.foo”; kiedy chcesz usunąć zdarzenie, możesz powiedzieć jQuery „usuń wszystkie zdarzenia foo” bez konieczności znajomości konkretnego modułu obsługi lub usuwania innych funkcji obsługi).

machineghost
źródło
Jestem świadomy problemu z IE. Tworzę aplikację, która w dużym stopniu opiera się na płótnie, więc IE7- są niedostępne. IE8 obsługuje kanwę, ale przynajmniej. IE9 + obsługuje addEventListener. Zdarzenia Namespaced jQuery wyglądają bardzo schludnie. Jedyne, o co się martwię, to wydajność.
takfuruya,
Ludzie z jQuery bardzo ciężko pracują, aby ich biblioteka działała dobrze, więc nie martwiłbym się o to zbytnio. Jednak biorąc pod uwagę surowe wymagania dotyczące przeglądarki, możesz zamiast tego sprawdzić Zepto. To trochę jak pomniejszona wersja jQuery, która jest szybsza, ale nie obsługuje starszych przeglądarek (i ma inne ograniczenia).
machineghost
Zdarzenia w przestrzeni nazw JQuery są szeroko stosowane i praktycznie nie mają problemów z wydajnością. Mówienie komuś, aby nie korzystał z narzędzia, które ułatwi jego kod i (prawdopodobnie co ważniejsze) łatwiejsze do zrozumienia, byłoby okropną radą, zwłaszcza jeśli zrobiono to z irracjonalnego strachu przed JQuery i wyimaginowanymi problemami z wydajnością.
machineghost
1
Który to podpis? Strona MDN na removeEventListener pokazuje, że oba pierwsze dwa argumenty są wymagane.
Coderer
Mój błąd. Minęły lata, odkąd napisałem tę odpowiedź, ale musiałem myśleć o jQuery offlub unbindmetodzie. Aby usunąć wszystkich słuchaczy z elementu, musisz śledzić ich dodawanie (co może zrobić za Ciebie jQuery lub inne biblioteki).
machineghost
1

Rozwiązanie jQuery:

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

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

$elem.off('click', $.proxy(object.method, object));
Ed Kolosovsky
źródło
1

Mieliśmy ten problem z biblioteką, której nie mogliśmy zmienić. Office Fabric UI, co oznaczało, że nie mogliśmy zmienić sposobu, w jaki zostały dodane programy obsługi zdarzeń. Sposób, w jaki to rozwiązaliśmy, polegał na nadpisaniu addEventListenerna EventTargetprototypie.

Spowoduje to dodanie nowej funkcji do obiektów element.removeAllEventListers("click")

(oryginalny post: Usuń uchwyt kliknięcia z nakładki okna dialogowego tkaniny )

        <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>
Piotr
źródło
0

Oto rozwiązanie:

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 Winnicki
źródło
0

można użyć o 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
źródło
-1

Jeśli chcesz użyć opcji „onclick”, jak zasugerowano powyżej, możesz spróbować tego:

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

Mam nadzieję, że to pomoże.

Diogo Schneider
źródło
-2

Minęło trochę czasu, ale MDN ma na to super wyjaśnienie. To pomogło mi bardziej niż rzeczy tutaj.

MDN :: EventTarget.addEventListener - wartość „this” w procedurze obsługi

Stanowi doskonałą alternatywę dla funkcji handleEvent.

Oto przykład z bindem i bez:

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
}

Problem w powyższym przykładzie polega na tym, że nie można usunąć nasłuchiwania za pomocą bind. Innym rozwiązaniem jest użycie specjalnej funkcji o nazwie handleEvent do przechwytywania zdarzeń:

Noitidart
źródło