การลบฟังเหตุการณ์ที่ถูกเพิ่มเข้ามาด้วยการผูก


164

ใน JavaScript วิธีที่ดีที่สุดในการลบฟังก์ชั่นที่เพิ่มเข้ามาเป็นฟังเหตุการณ์โดยใช้ bind () คืออะไร?

ตัวอย่าง

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

})();

วิธีเดียวที่ฉันคิดได้คือการติดตามผู้ฟังทุกคนที่เพิ่มเข้ามาด้วยการผูก

ตัวอย่างข้างต้นด้วยวิธีนี้:

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

})();

มีวิธีที่ดีกว่าในการทำเช่นนี้?


2
สิ่งที่คุณกำลังทำยกเว้นthis.clickListener = this.clickListener.bind(this);และthis.myButton.addEventListener("click", this.clickListener);
Esailija

ว่าเป็นสิ่งที่ดีมาก. นี่อาจเป็นหัวข้อที่แตกต่างกัน แต่มันทำให้ฉันสงสัยว่าฉันควรจะผูก (นี่) สำหรับส่วนที่เหลือของวิธีการของฉันที่ใช้คำสำคัญ "นี้" แม้ว่ามันจะทำให้วิธีการโทรไม่มีประสิทธิภาพ
takfuruya

ฉันมักจะทำสิ่งนี้เป็นสิ่งแรกในตัวสร้างสำหรับวิธีการทั้งหมดที่จะถูกส่งผ่านที่ไหนสักแห่งโดยไม่คำนึงว่าฉันจะลบพวกเขาในภายหลัง แต่ไม่ใช่สำหรับวิธีการทั้งหมดเท่านั้นที่ผ่านไป
Esailija

สิ่งที่คุณทำนั้นสมเหตุสมผล แต่ถ้านี่เป็นส่วนหนึ่งของห้องสมุดคุณจะไม่มีทางรู้ว่า MyClass 'เมธอดใด (บันทึกว่าเป็น "สาธารณะ") จะถูกส่งต่อไป
takfuruya

เพียงแค่ FYI ไลบรารี Underscore มีbindAllฟังก์ชันที่ช่วยลดความยุ่งยากของเมธอด ภายในเครื่องมือเริ่มต้นวัตถุของคุณคุณเพียงแค่_.bindAll(this)ตั้งค่าทุกวิธีในวัตถุของคุณให้เป็นรุ่นที่ถูกผูกไว้ หรือถ้าคุณเพียงต้องการที่จะผูกวิธีการบางอย่าง (ซึ่งผมอยากแนะนำให้เพื่อป้องกันการรั่วไหลของหน่วยความจำที่ไม่ได้ตั้งใจ) _.bindAll(this, "foo", "bar") // this.baz won't be boundคุณสามารถให้พวกเขาเป็นอาร์กิวเมนต์:
machineghost

คำตอบ:


274

แม้ว่าสิ่งที่ @machineghost พูดว่าเป็นความจริง แต่เหตุการณ์ที่เพิ่มและลบออกในลักษณะเดียวกันส่วนที่ขาดหายไปของสมการคือ:

การอ้างอิงฟังก์ชั่นใหม่ถูกสร้างขึ้นหลังจาก.bind()ถูกเรียก!

ดูการผูก () เปลี่ยนการอ้างอิงฟังก์ชันหรือไม่ | วิธีการตั้งค่าอย่างถาวร?

ดังนั้นหากต้องการเพิ่มหรือลบออกให้กำหนดการอ้างอิงกับตัวแปร:

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

ทำงานได้ตามที่คาดไว้สำหรับฉัน


4
ดีมากนี่ควรเป็นคำตอบที่ยอมรับได้ ขอบคุณที่อัปเดตหัวข้อเก่าหัวข้อนี้ปรากฏขึ้นในเครื่องมือค้นหาว่าเป็นที่นิยมอันดับหนึ่งและไม่มีทางออกที่เหมาะสมจนกว่าคุณจะโพสต์ข้อความนี้
Blargh

นี่ไม่แตกต่างจาก (และไม่ดีไปกว่า) วิธีการที่กล่าวถึงในคำถาม
Peter Tseng

ฉันไม่สามารถเข้าใจได้อย่างไรคุณทำให้มันทำงานกับเหตุการณ์คลิกได้อย่างไรขอบคุณ
อัลเบอร์โตอคูนา

@ AlbertoAcuñaเบราว์เซอร์สมัยใหม่ใช้.addEventListener(type, listener)และ.removeEventListener(type, listener)เพื่อเพิ่มและลบกิจกรรมในองค์ประกอบ สำหรับทั้งคู่คุณสามารถส่งผ่านการอ้างอิงฟังก์ชันที่อธิบายไว้ในโซลูชันเป็นlistenerพารามิเตอร์พร้อมกับ"click"ประเภท developer.mozilla.org/en-US/docs/Web/API/EventTarget/...
เบน

1
นี่ช่วยฉันได้แม้คำตอบนี้โพสต์เมื่อ 4 ปีก่อน :)
user2609021

46

สำหรับผู้ที่มีปัญหานี้ขณะลงทะเบียน / ลบผู้ฟังของ React component ไปยัง / จาก Flux store ให้เพิ่มบรรทัดด้านล่างลงใน Constructor ของ component ของคุณ:

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
กลลวงดี แต่ React / Flux เกี่ยวข้องกับอะไรบ้าง
Peter Tseng

สิ่งนี้ดูเหมือนจะเป็นวิธีที่ถูกต้องเมื่อเพิ่มและลบฟังเหตุการณ์จากคลาสหรือฟังก์ชันต้นแบบที่แตกต่างกันซึ่งฉันเชื่อว่าการเชื่อมต่อกับสิ่งนี้ยังใช้กับส่วนประกอบ / คลาสของ React ด้วย คุณกำลังผูกไว้ที่ระดับอินสแตนซ์ทั่วไป (เช่น root)
Keith DC

1
this.onChange = this.onChange.bind(this)จริงๆแล้วนี่คือสิ่งที่ฉันกำลังมองหา ฟังก์ชั่นนี้ถูกผูกไว้thisตลอดไป :)
Paweł

2

ไม่สำคัญว่าคุณจะใช้ฟังก์ชั่นที่ถูกผูกไว้หรือไม่ คุณลบแบบเดียวกับตัวจัดการเหตุการณ์อื่น ๆ หากปัญหาของคุณคือรุ่นที่ถูกผูกไว้เป็นฟังก์ชันเฉพาะของตัวเองคุณสามารถติดตามเวอร์ชันที่ถูกผูกไว้หรือใช้removeEventListenerลายเซ็นที่ไม่ใช้ตัวจัดการเฉพาะ (แม้ว่าแน่นอนว่าจะลบตัวจัดการเหตุการณ์ประเภทเดียวกัน )

(ในฐานะที่เป็นข้อความด้านข้าง, addEventListenerไม่สามารถใช้ได้กับทุกเบราว์เซอร์คุณควรใช้ไลบรารี่เช่น jQuery เพื่อทำกิจกรรมของคุณในลักษณะข้ามเบราว์เซอร์สำหรับคุณนอกจากนี้ jQuery ยังมีแนวคิดของเหตุการณ์เนมสเปซ คุณจะผูกกับ "click.foo"; เมื่อคุณต้องการลบกิจกรรมคุณสามารถบอก jQuery "ลบกิจกรรม foo ทั้งหมด" โดยไม่ต้องรู้จักตัวจัดการที่เฉพาะเจาะจงหรือลบตัวจัดการอื่น ๆ )


ฉันตระหนักถึงปัญหา IE ฉันกำลังพัฒนาแอพพลิเคชั่นที่ต้องอาศัยผ้าใบเป็นอย่างมากดังนั้น IE7- ออกแล้ว IE8 รองรับ canvas แต่อย่างน้อยที่สุด IE9 + รองรับ addEventListener กิจกรรม Namespaced ของ jQuery ดูเรียบร้อยมาก สิ่งเดียวที่ฉันกังวลคือประสิทธิภาพ
takfuruya

คน jQuery ทำงานอย่างหนักเพื่อให้ห้องสมุดของพวกเขาทำงานได้ดีดังนั้นฉันจะไม่กังวลมากเกินไป อย่างไรก็ตามตามข้อกำหนดของเบราว์เซอร์ที่เข้มงวดคุณอาจต้องการตรวจสอบ Zepto แทน มันคล้ายกับ jQuery รุ่นที่ปรับขนาดได้เร็วกว่า แต่ไม่สามารถรองรับเบราว์เซอร์รุ่นเก่า (และมีข้อ จำกัด อื่น ๆ )
machineghost

เหตุการณ์ namespaced JQuery มีการใช้กันอย่างแพร่หลายและแทบไม่มีความกังวลเกี่ยวกับประสิทธิภาพ การบอกใครสักคนที่จะไม่ใช้เครื่องมือที่จะทำให้รหัสของพวกเขาง่ายขึ้นและที่สำคัญกว่านั้นคือเข้าใจได้ง่ายขึ้นจะเป็นคำแนะนำที่น่ากลัวโดยเฉพาะอย่างยิ่งหากทำโดยไม่ต้องกลัว JQuery และการจินตนาการ
machineghost

1
ลายเซ็นไหนที่จะเป็นอย่างไร หน้า MDN ใน removeEventListenerแสดงให้เห็นว่าทั้งสองข้อแรกจำเป็นต้องมีข้อโต้แย้ง
Coderer

ความผิดพลาดของฉัน. เป็นเวลาหลายปีแล้วที่ฉันเขียนคำตอบนั้น แต่ฉันต้องคิดถึง jQuery's offหรือunbindวิธีการ ในการลบผู้ฟังทั้งหมดในองค์ประกอบคุณต้องติดตามพวกเขาเมื่อพวกเขาได้รับการเพิ่ม (ซึ่งเป็นสิ่ง jQuery หรือห้องสมุดอื่น ๆ สามารถทำเพื่อคุณ)
machineghost


1

เราพบปัญหานี้กับห้องสมุดที่เราไม่สามารถเปลี่ยนแปลงได้ Office Fabric UI ซึ่งหมายความว่าเราไม่สามารถเปลี่ยนวิธีเพิ่มตัวจัดการเหตุการณ์ วิธีที่เราแก้ไขมันคือเขียนทับaddEventListenerตัวEventTargetต้นแบบ

สิ่งนี้จะเพิ่มฟังก์ชั่นใหม่บนวัตถุ element.removeAllEventListers("click")

(โพสต์ต้นฉบับ: ลบตัวจัดการคลิกจากการซ้อนทับกล่องโต้ตอบผ้า )

        <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

นี่คือทางออก:

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:

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

หากคุณต้องการใช้ 'onclick' ตามที่แนะนำข้างต้นคุณสามารถลอง:

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

ฉันหวังว่ามันจะช่วย


-2

ไม่นานมานี้ แต่ MDN มีคำอธิบายที่ดีเกี่ยวกับเรื่องนี้ ที่ช่วยฉันมากกว่าสิ่งที่นี่

MDN :: EventTarget.addEventListener - ค่าของ "this" ภายในตัวจัดการ

มันเป็นทางเลือกที่ยอดเยี่ยมสำหรับฟังก์ชั่น handleEvent

นี่คือตัวอย่างที่มีและไม่มีการผูก:

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
}

ปัญหาในตัวอย่างด้านบนคือคุณไม่สามารถลบ listener ด้วย bind โซลูชันอื่นใช้ฟังก์ชันพิเศษที่เรียกว่า handleEvent เพื่อตรวจจับเหตุการณ์ใด ๆ :

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.