Bağlama ile eklenen olay dinleyicisini kaldırma


165

JavaScript'te, olay dinleyicisi olarak eklenen bir işlevi bind () kullanarak kaldırmanın en iyi yolu nedir?

Misal

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

})();

Aklıma gelen tek yol bağlama ile eklenen her dinleyiciyi takip etmektir.

Bu yöntemle yukarıdaki örnek:

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

})();

Bunu yapmanın daha iyi yolları var mı?


2
Dışında ne yapıyorsun this.clickListener = this.clickListener.bind(this);vethis.myButton.addEventListener("click", this.clickListener);
Esailija

Bu çok güzel. Bu farklı bir konu olabilir, ancak yöntem çağrılarını etkisiz hale getirse bile "this" anahtar sözcüğünü kullanan yöntemlerimin geri kalanı için bağlama (bu) olup olmadığını merak ettim.
takfuruya

Bunu daha sonra kaldıracağımdan bağımsız olarak, bir yerde geçirilecek tüm yöntemler için her zaman yapıcıda ilk şey olarak yaparım. Ancak tüm yöntemler için değil, sadece aktarılanlar için.
Esailija

Yaptığın şey mantıklı. Ancak bu, örneğin bir kütüphanenin parçasıysa, hangi MyClass yöntemlerinin ("genel" olarak belgelenmiş) aktarılacağını asla bilemezsiniz.
takfuruya

Sadece FYI, Alt çizgi kitaplığı bindAllbağlama yöntemlerini basitleştiren bir işleve sahiptir. Nesne başlatıcısının içinde, nesnenizdeki _.bindAll(this)her yöntemi bağlı bir sürüme ayarlamak için yapmanız yeterlidir. Alternatif olarak, yalnızca (I öneriyoruz yanlışlıkla bellek sızıntılarını önlemek için) bazı yöntemler bağlamak istiyorsanız, argüman olarak sağlayabilir: _.bindAll(this, "foo", "bar") // this.baz won't be bound.
machineghost

Yanıtlar:


274

@Machineghost'un söylediği doğru olmasına rağmen, olaylar aynı şekilde eklenir ve kaldırılır, denklemin eksik kısmı şuydu:

Yeni bir işlev referansı .bind()çağrıldıktan sonra oluşturulur !

Bkz. Bind () işlev referansını değiştiriyor mu? | Kalıcı olarak nasıl ayarlanır?

Bu nedenle, eklemek veya kaldırmak için başvuruyu bir değişkene atayın:

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

Bu benim için beklendiği gibi çalışıyor.


4
Mükemmel, bu kabul edilen cevap olmalı. Eski bir konuyu güncellediğiniz için teşekkür ederiz, bu konu arama motorunda bir numaralı hit olarak ortaya çıktı ve şimdi yayınlayana kadar uygun bir çözümü yoktu.
Blargh

Bu, soruda bahsedilen yöntemden farklı değildir (ve bundan daha iyi değildir).
Peter Tseng

Anlayamıyorum, nasıl bir tıklama olayı ile çalışmasını sağlıyorsunuz, teşekkürler
Alberto Acuña

@ AlbertoAcuña Modern tarayıcılar kullanmak .addEventListener(type, listener)ve .removeEventListener(type, listener)bir elemanda olayları eklemek ve kaldırmak için. Her ikisi için de, çözümde açıklanan işlev referansını tür olarak listenerparametre olarak iletebilirsiniz "click". developer.mozilla.org/tr-TR/docs/Web/API/EventTarget/…
Ben

1
Bu cevap bana yardımcı olur-gerçi bu cevap 4 yıl önce gönderildi :)
user2609021

46

React bileşeninin dinleyicisini Flux deposuna kaydederken / kaldırırken bu sorunu yaşayanlar için, aşağıdaki satırları bileşeninizin yapıcısına ekleyin:

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
Güzel numara, ama React / Flux'un herhangi bir şeyle ne ilgisi var?
Peter Tseng

Farklı sınıflardan veya prototip işlevlerden olay dinleyicileri eklerken ve kaldırırken bu doğru yaklaşım gibi görünüyor, ki bununla bağlantıyı React bileşenleri / sınıfları için de geçerli olduğuna inanıyorum. Ortak (örneğin, kök) bir örnek düzeyinde bağlarsınız.
Keith DC

1
this.onChange = this.onChange.bind(this)aslında aradığım şey buydu. İşlev thissonsuza kadar bağlı :)
Paweł

2

Bağlı bir işlev kullanıp kullanmadığınız önemli değildir; diğer olay işleyicileriyle aynı şekilde kaldırırsınız. Sorununuz, ilişkili sürümün kendi benzersiz işlevi olması durumunda, ilişkili sürümleri takip edebilir veya removeEventListenerbelirli bir işleyici almayan imzayı kullanabilirsiniz (tabii ki aynı türdeki diğer olay işleyicilerini kaldıracaktır) ).

(Bir yan not olarak, addEventListenertüm tarayıcılarda çalışmaz; etkinlik kancalarınızı sizin için çapraz tarayıcı bir şekilde yapmak için gerçekten jQuery gibi bir kitaplık kullanmalısınız. Ayrıca, jQuery, "click.foo" ya bağlamanız gerekir; olayı kaldırmak istediğinizde, jQuery'ye belirli işleyiciyi bilmek veya diğer işleyicileri kaldırmak zorunda kalmadan "tüm foo olaylarını kaldırmasını" söyleyebilirsiniz.)


IE sorununun farkındayım. IE7- çok tuval üzerine dayanan bir uygulama geliştiriyorum. IE8 tuvali destekler ancak en azından. IE9 +, addEventListener'ı destekler. jQuery'nin Namespaced Etkinlikleri çok düzgün görünüyor. Endişelendiğim tek şey verimlilik.
takfuruya

JQuery millet kendi kütüphane iyi performans tutmak için çok çalışıyoruz , bu yüzden bu konuda çok endişe olmaz. Ancak, katı tarayıcı gereksinimleriniz göz önüne alındığında, bunun yerine Zepto'ya göz atmak isteyebilirsiniz. JQuery'nin daha hızlı olan ancak eski tarayıcıları destekleyemeyen (ve diğer bazı sınırları olan) ölçeklendirilmiş bir sürümü gibi.
machineghost

JQuery ad alanı olayları yaygın olarak kullanılmaktadır ve neredeyse hiçbir performans kaygısı yoktur. Birisine kodlarını daha kolay ve (muhtemelen daha da önemlisi) anlaşılmasını kolaylaştıracak bir araç kullanmamasını söylemek, özellikle de JQuery'nin mantıksız korkusundan ve hayali performans endişelerinden kaynaklanıyorsa, korkunç bir tavsiye olacaktır.
machineghost

1
Hangi imza olurdu? RemoveEventListener'daki MDN sayfası , ilk iki bağımsız değişkenin her ikisinin de gerekli olduğunu gösterir.
Coderer

Benim hatam. Bu cevabı yazdığımdan beri yıllar geçti, ama jQuery offveya unbindmetodunu düşünüyor olmalıydım . Bir öğedeki tüm dinleyicileri kaldırmak için, eklendikçe onları izlemeniz gerekir (bu, jQuery veya diğer kütüphanelerin sizin için yapabileceği bir şeydir).
machineghost

1

jQuery çözümü:

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

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

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

1

Değiştiremediğimiz bir kütüphane ile bu sorunu yaşadık. Office Fabric kullanıcı arabirimi, etkinlik işleyicilerinin eklenme şeklini değiştiremediğimiz anlamına geliyordu. Bunu çözmüş yolu üzerine yazmak oldu addEventListenerüzerindeki EventTargetprototip.

Bu, nesnelere yeni bir işlev ekleyecektir element.removeAllEventListers("click")

(orijinal gönderi: Tıklama işleyicisini kumaş iletişim kutusundan kaldır )

        <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

İşte çözüm:

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

ES7 hakkında kullanabilirsiniz:

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

Yukarıda önerildiği gibi 'onclick'i kullanmak istiyorsanız şunu deneyebilirsiniz:

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

Umut ediyorum bu yardım eder.


-2

Kısa bir süre oldu, ancak MDN'nin bu konuda süper bir açıklaması var. Bu bana buradaki şeylerden daha fazla yardımcı oldu.

MDN :: EventTarget.addEventListener - İşleyici içindeki "this" değeri

HandleEvent işlevine mükemmel bir alternatif sunar.

Bu, bağlama içeren ve bağlamayan bir örnektir:

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
}

Yukarıdaki örnekte görülen bir sorun, dinleyiciyi bağlama ile kaldıramayacağınızdır. Başka bir çözüm, olayları yakalamak için handleEvent adlı özel bir işlev kullanmaktır:

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.