Menghapus pendengar acara yang ditambahkan dengan bind

164

Dalam JavaScript, apa cara terbaik untuk menghapus fungsi yang ditambahkan sebagai pendengar acara menggunakan bind ()?

Contoh

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

})();

Satu-satunya cara yang bisa saya pikirkan adalah melacak setiap pendengar yang ditambahkan dengan bind.

Contoh di atas dengan metode ini:

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

})();

Apakah ada cara yang lebih baik untuk melakukan ini?

takfuruya
sumber
2
Apa yang Anda lakukan kecuali this.clickListener = this.clickListener.bind(this);danthis.myButton.addEventListener("click", this.clickListener);
Esailija
Itu sangat bagus. Ini mungkin topik yang berbeda, tapi itu membuat saya bertanya-tanya apakah saya harus mengikat (ini) untuk sisa metode saya yang menggunakan kata kunci "ini" meskipun itu akan membuat panggilan metode tidak efisien.
takfuruya
Saya selalu melakukan ini sebagai hal pertama dalam konstruktor untuk semua metode yang akan dilewati di suatu tempat, terlepas dari apakah saya akan menghapusnya nanti. Tetapi tidak untuk semua metode, hanya metode yang diedarkan saja.
Esailija
Apa yang Anda lakukan masuk akal. Tetapi jika ini adalah bagian dari perpustakaan, misalnya, Anda tidak akan pernah tahu metode MyClass mana (didokumentasikan sebagai "publik") yang akan diedarkan.
takfuruya
Hanya FYI, pustaka Garis Bawah memiliki bindAllfungsi yang menyederhanakan metode pengikatan. Di dalam penginisialisasi objek Anda, Anda hanya _.bindAll(this)perlu mengatur setiap metode di objek Anda ke versi terikat. Atau, jika Anda hanya ingin mengikat beberapa metode (yang saya akan merekomendasikan, untuk mencegah kebocoran memori disengaja), Anda dapat memberikan mereka sebagai argumen: _.bindAll(this, "foo", "bar") // this.baz won't be bound.
machineghost

Jawaban:

274

Meskipun apa yang dikatakan @machineghost benar, bahwa peristiwa ditambahkan dan dihapus dengan cara yang sama, bagian persamaan yang hilang adalah sebagai berikut:

Referensi fungsi baru dibuat setelah .bind()disebut!

Lihat Apakah bind () mengubah referensi fungsi? | Bagaimana cara mengatur secara permanen?

Jadi, untuk menambah atau menghapusnya, tetapkan referensi ke variabel:

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

Ini berfungsi seperti yang diharapkan untuk saya.

Ben
sumber
4
Luar biasa, ini harus menjadi jawaban yang diterima. Terima kasih telah memperbarui topik lama, topik ini muncul di mesin pencari sebagai hit nomor satu dan tidak memiliki solusi yang tepat sampai Anda memposting ini sekarang.
Blargh
Ini tidak berbeda dengan (dan tidak lebih baik dari) metode yang disebutkan dalam pertanyaan.
Peter Tseng
Saya tidak mengerti, bagaimana cara membuatnya bekerja dengan acara klik, terima kasih
Alberto Acuña
@ AlbertoAcuña Browser modern menggunakan .addEventListener(type, listener)dan .removeEventListener(type, listener)untuk menambah dan menghapus acara pada suatu elemen. Untuk keduanya, Anda dapat melewatkan referensi fungsi yang dijelaskan dalam solusi sebagai listenerparameter, dengan "click"sebagai tipe. developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
Ben
1
ini membantu saya walaupun jawaban ini diposting 4 tahun yang lalu :)
user2609021
46

Bagi mereka yang memiliki masalah ini saat mendaftar / menghapus pendengar komponen Bereaksi ke / dari toko Flux, tambahkan baris di bawah ini ke konstruktor komponen Anda:

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
sumber
7
Trik yang bagus, tetapi apa yang harus dilakukan React / Flux dengan apa pun?
Peter Tseng
Ini tampaknya menjadi pendekatan yang benar ketika menambahkan dan menghapus acara pendengar dari kelas yang berbeda atau fungsi prototipe, yang saya percaya koneksi dengan ini juga berlaku untuk komponen Bereaksi / kelas. Anda mengikatnya pada tingkat instance umum (misalnya, root).
Keith DC
1
this.onChange = this.onChange.bind(this)sebenarnya ini yang saya cari. Fungsi ini diikat thisselamanya :)
Paweł
2

Tidak masalah apakah Anda menggunakan fungsi terikat atau tidak; Anda menghapusnya dengan cara yang sama seperti pengendali acara lainnya. Jika masalah Anda adalah bahwa versi terikat adalah fungsi uniknya sendiri, Anda dapat melacak versi terikat, atau menggunakan removeEventListenertanda tangan yang tidak mengambil penangan tertentu (walaupun tentu saja itu akan menghapus penangan acara lainnya dari jenis yang sama ).

(Sebagai catatan, addEventListenertidak berfungsi di semua browser; Anda benar-benar harus menggunakan perpustakaan seperti jQuery untuk melakukan hook-up acara Anda dengan cara lintas-browser untuk Anda. Juga, jQuery memiliki konsep acara dengan namespace, yang memungkinkan Anda mengikat "click.foo"; ketika Anda ingin menghapus acara, Anda dapat memberi tahu jQuery "hapus semua acara foo" tanpa harus mengetahui penangan spesifik atau menghapus penangan lainnya.)

machineghost
sumber
Saya mengetahui masalah IE. Saya sedang mengembangkan aplikasi yang sangat bergantung pada kanvas sehingga IE7- keluar. IE8 mendukung kanvas tetapi minimal. IE9 + mendukung addEventListener. Acara james dari Namespaced terlihat sangat rapi. Satu-satunya hal yang saya khawatirkan adalah efisiensi.
takfuruya
Orang-orang jQuery bekerja sangat keras untuk menjaga perpustakaan mereka berkinerja baik, jadi saya tidak akan terlalu khawatir tentang itu. Namun, mengingat persyaratan browser Anda yang ketat, Anda mungkin ingin melihat Zepto sebagai gantinya. Ini semacam versi jQuery yang diperkecil yang lebih cepat tetapi tidak dapat mendukung peramban yang lebih lama (dan memiliki beberapa batasan lain).
machineghost
Peristiwa namespace JQuery banyak digunakan dan hampir tidak memiliki masalah kinerja. Memberitahu seseorang untuk tidak menggunakan alat yang akan membuat kode mereka lebih mudah dan (bisa dibilang lebih penting) lebih mudah dimengerti, akan menjadi nasihat yang mengerikan, terutama jika dilakukan karena ketakutan yang tidak rasional terhadap JQuery dan masalah kinerja imajiner.
machineghost
1
Tanda tangan apakah itu? Halaman MDN pada removeEventListener menunjukkan bahwa kedua argumen pertama diperlukan.
Coderer
Kesalahanku. Sudah bertahun-tahun sejak saya menulis jawaban itu, tetapi saya pasti telah memikirkan jQuery offatau unbindmetode. Untuk menghapus semua pendengar pada suatu elemen Anda harus melacak mereka ketika mereka ditambahkan (yang merupakan sesuatu yang dapat dilakukan jQuery atau perpustakaan lain untuk Anda).
machineghost
1

solusi jQuery:

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

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

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

Kami memiliki masalah dengan perpustakaan yang tidak dapat kami ubah. Office Fabric UI, yang berarti kami tidak bisa mengubah cara penangan acara ditambahkan. Cara kita dipecahkan itu untuk menimpa addEventListenerpada EventTargetprototipe.

Ini akan menambahkan fungsi baru pada objek element.removeAllEventListers("click")

(pos asli: Hapus handler Klik dari hamparan dialog kain )

        <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
sumber
0

Ini solusinya:

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
sumber
0

dapat menggunakan tentang 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
sumber
-1

Jika Anda ingin menggunakan 'onclick', seperti yang disarankan di atas, Anda dapat mencoba ini:

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

Saya harap ini membantu.

Diogo Schneider
sumber
-2

Sudah beberapa saat tetapi MDN memiliki penjelasan super tentang ini. Itu membantu saya lebih daripada hal-hal di sini.

MDN :: EventTarget.addEventListener - Nilai "ini" dalam handler

Ini memberikan alternatif yang bagus untuk fungsi handleEvent.

Ini adalah contoh dengan dan tanpa ikatan:

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
}

Masalah dalam contoh di atas adalah bahwa Anda tidak dapat menghapus pendengar dengan bind. Solusi lain adalah menggunakan fungsi khusus yang disebut handleEvent untuk menangkap setiap peristiwa:

Noitidart
sumber