Loại bỏ trình nghe sự kiện đã được thêm bằng liên kết


164

Trong JavaScript, cách tốt nhất để loại bỏ một hàm được thêm vào như một trình lắng nghe sự kiện bằng cách sử dụng bind () là gì?

Thí dụ

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

})();

Cách duy nhất tôi có thể nghĩ đến là theo dõi mọi người nghe được thêm vào với ràng buộc.

Ví dụ trên với phương pháp này:

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

})();

Có cách nào tốt hơn để làm điều này?


2
Những gì bạn đang làm ngoại trừ this.clickListener = this.clickListener.bind(this);this.myButton.addEventListener("click", this.clickListener);
Esailija

Thật tuyệt. Đây có thể là một chủ đề khác, nhưng nó khiến tôi tự hỏi liệu tôi có nên thực hiện liên kết (cái này) cho các phương thức còn lại sử dụng từ khóa "này" hay không mặc dù nó sẽ khiến các cuộc gọi phương thức không hiệu quả.
takfuruya

Tôi luôn luôn làm điều này như là một điều đầu tiên trong hàm tạo cho tất cả các phương thức sẽ được truyền đi đâu đó, bất kể tôi sẽ loại bỏ chúng sau này. Nhưng không phải cho tất cả các phương pháp, chỉ những phương pháp được thông qua.
Esailija

Những gì bạn đang làm có ý nghĩa. Nhưng nếu đây là một phần của thư viện, chẳng hạn, bạn không bao giờ có thể biết phương thức nào của MyClass (được ghi là "công khai") sẽ được thông qua.
takfuruya

Chỉ cần FYI, thư viện Underscore có bindAllchức năng đơn giản hóa các phương thức ràng buộc. Bên trong trình khởi tạo đối tượng của bạn, bạn chỉ cần thực hiện _.bindAll(this)để đặt mọi phương thức trong đối tượng của mình thành một phiên bản ràng buộc. Ngoài ra, nếu bạn chỉ muốn liên kết một số phương thức (mà tôi khuyên dùng, để ngăn chặn rò rỉ bộ nhớ ngẫu nhiên), bạn có thể cung cấp chúng dưới dạng đối số : _.bindAll(this, "foo", "bar") // this.baz won't be bound.
máy vào

Câu trả lời:


274

Mặc dù những gì @machineghost nói là đúng, nhưng các sự kiện được thêm và xóa theo cùng một cách, phần còn thiếu của phương trình là:

Một tham chiếu chức năng mới được tạo sau khi .bind()được gọi!

Xem Liệu bind () thay đổi tham chiếu hàm? | Làm thế nào để thiết lập vĩnh viễn?

Vì vậy, để thêm hoặc xóa nó, gán tham chiếu cho một biến:

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

Điều này làm việc như mong đợi cho tôi.


4
Tuyệt vời, đây phải là câu trả lời được chấp nhận. Cảm ơn bạn đã cập nhật một chủ đề cũ, chủ đề này đã xuất hiện trên công cụ tìm kiếm như là một điểm nhấn số một và nó không có giải pháp thích hợp cho đến khi bạn đăng bài này ngay bây giờ.
Blargh

Điều này không khác với (và không tốt hơn) phương pháp được đề cập trong câu hỏi.
Peter Tseng

Tôi không thể hiểu, làm thế nào để bạn làm cho nó hoạt động với một sự kiện nhấp chuột, cảm ơn
Alberto Acuña

@ AlbertoAcuña Các trình duyệt hiện đại sử dụng .addEventListener(type, listener).removeEventListener(type, listener)để thêm và xóa các sự kiện trên một phần tử. Đối với cả hai, bạn có thể truyền tham chiếu hàm được mô tả trong giải pháp dưới dạng listenertham số, với "click"kiểu là.developer.mozilla.org/en-US/docs/Web/API/EventTarget/
Ben

1
điều này giúp tôi ngay cả - mặc dù câu trả lời này được đăng 4 năm trước :)
user2609021

46

Đối với những người gặp vấn đề này trong khi đăng ký / xóa trình nghe thành phần React đến / từ cửa hàng Flux, hãy thêm các dòng bên dưới vào hàm tạo của thành phần của bạn:

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
Thủ thuật hay, nhưng React / Flux có liên quan gì?
Peter Tseng

Đây dường như là cách tiếp cận chính xác khi thêm và xóa trình lắng nghe sự kiện khỏi các lớp khác nhau hoặc các hàm nguyên mẫu, tôi tin rằng kết nối với điều này cũng áp dụng cho các thành phần / lớp React. Bạn đang ràng buộc nó ở một mức độ phổ biến (ví dụ: root).
Keith DC

1
this.onChange = this.onChange.bind(this)thực sự đây là những gì tôi đang tìm kiếm. Chức năng bị ràng buộc thismãi mãi :)
Paweł

2

Không quan trọng bạn có sử dụng chức năng ràng buộc hay không; bạn loại bỏ nó giống như bất kỳ trình xử lý sự kiện nào khác. Nếu vấn đề của bạn là phiên bản bị ràng buộc là chức năng duy nhất của riêng nó, bạn có thể theo dõi các phiên bản bị ràng buộc hoặc sử dụngremoveEventListener chữ ký không có trình xử lý cụ thể (mặc dù tất nhiên sẽ loại bỏ các trình xử lý sự kiện khác cùng loại ).

(Như một lưu ý phụ, addEventListenerkhông hoạt động trong tất cả các trình duyệt; bạn thực sự nên sử dụng một thư viện như jQuery để thực hiện các kết nối sự kiện của mình theo cách trình duyệt chéo cho bạn. bạn liên kết với "click.foo"; khi bạn muốn xóa sự kiện, bạn có thể nói với jQuery "xóa tất cả các sự kiện foo" mà không cần phải biết trình xử lý cụ thể hoặc loại bỏ các trình xử lý khác.)


Tôi biết vấn đề IE. Tôi đang phát triển một ứng dụng dựa nhiều vào canvas để IE7- không hoạt động. IE8 hỗ trợ canvas nhưng ở mức tối thiểu. IE9 + hỗ trợ addEventListener. Các sự kiện không gian tên của jQuery trông rất gọn gàng. Điều duy nhất tôi lo lắng là hiệu quả.
takfuruya

Các jQuery làm việc rất chăm chỉ để giữ cho thư viện của họ hoạt động tốt, vì vậy tôi sẽ không lo lắng về điều đó quá nhiều. Tuy nhiên, với các yêu cầu trình duyệt nghiêm ngặt của bạn, bạn có thể muốn kiểm tra Zepto thay thế. Nó giống như một phiên bản thu nhỏ của jQuery nhanh hơn nhưng không thể hỗ trợ các trình duyệt cũ hơn (và có một số giới hạn khác).
máy vào

Các sự kiện được đặt tên của JQuery được sử dụng rộng rãi và hầu như không có mối quan tâm về hiệu năng. Nói với ai đó không sử dụng một công cụ sẽ giúp mã của họ dễ dàng hơn và (có thể nói là quan trọng hơn) dễ hiểu hơn, sẽ là lời khuyên khủng khiếp, đặc biệt là nếu làm như vậy vì nỗi sợ phi lý về mối quan tâm về hiệu năng tưởng tượng của JQuery.
máy vào

1
Chữ ký đó sẽ là gì? Trang MDN trên removeEventListener cho thấy cả hai đối số đầu tiên là bắt buộc.
Bộ giải mã

Lỗi của tôi. Đã nhiều năm kể từ khi tôi viết câu trả lời đó, nhưng tôi phải nghĩ về phương pháp offhoặc unbindphương pháp của jQuery . Để loại bỏ tất cả người nghe trên một phần tử, bạn phải theo dõi chúng khi chúng được thêm vào (đó là điều mà jQuery hoặc các thư viện khác có thể làm cho bạn).
máy vào

1

Giải pháp jQuery:

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

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

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

1

Chúng tôi đã có vấn đề này với một thư viện chúng tôi không thể thay đổi. Office Fabric UI, có nghĩa là chúng tôi không thể thay đổi cách xử lý sự kiện được thêm vào. Cách chúng tôi giải quyết nó là để ghi đè lên addEventListenertrênEventTarget nguyên mẫu.

Điều này sẽ thêm một chức năng mới trên các đối tượng element.removeAllEventListers("click")

(bài gốc: Xóa Trình xử lý nhấp chuột khỏi lớp phủ hộp thoại vải )

        <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

Đây là giải pháp:

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

có thể sử dụng khoảng 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

Nếu bạn muốn sử dụng 'onclick', như được đề xuất ở trên, bạn có thể thử điều này:

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

Tôi hy vọng nó sẽ giúp.


-2

Đã được một lúc nhưng MDN có một lời giải thích siêu về điều này. Điều đó đã giúp tôi nhiều hơn những thứ ở đây.

MDN :: EventTarget.addEventListener - Giá trị của "this" trong trình xử lý

Nó cung cấp một sự thay thế tuyệt vời cho chức năng handleEvent.

Đây là một ví dụ có và không có ràng buộc:

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
}

Một vấn đề trong ví dụ trên là bạn không thể loại bỏ trình nghe bằng liên kết. Một giải pháp khác là sử dụng một chức năng đặc biệt gọi là handleEvent để nắm bắt bất kỳ sự kiện nào:

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.