228

In JavaScript, what is the best way to remove a function added as an event listener using bind()?

Example

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

})();

The only way I can think of is to keep track of every listener added with bind.

Above example with this method:

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

})();

Are there any better ways to do this?

7
  • 4
    What you are doing except this.clickListener = this.clickListener.bind(this); and this.myButton.addEventListener("click", this.clickListener);
    – Esailija
    Commented Jul 19, 2012 at 16:46
  • That is very nice. This may be a different topic, but it made me wonder whether I should do bind(this) for the rest of my methods that use the "this" keyword even though it would make method calls inefficient.
    – takfuruya
    Commented Jul 19, 2012 at 17:23
  • I always do this as a first thing in the constructor for all the methods that are going to be passed somewhere, regardless if I am going to remove them later. But not for all methods, just those that are passed around.
    – Esailija
    Commented Jul 19, 2012 at 17:25
  • What you're doing makes sense. But if this was part of a library, for instance, you can never know which MyClass' methods (documented as being "public") would be passed around.
    – takfuruya
    Commented Jul 19, 2012 at 18:19
  • Just FYI, the Underscore library has a bindAll function that simplifies binding methods. Inside your object initializer you just do _.bindAll(this) to set every method in your object to a bound version. Alternatively, if you only want to bind some methods (which I'd recommend, to prevent accidental memory leaks), you can provide them as arguments: _.bindAll(this, "foo", "bar") // this.baz won't be bound. Commented Jul 19, 2012 at 18:25

10 Answers 10

361

Although what @machineghost said was true, that events are added and removed the same way, the missing part of the equation was this:

A new function reference is created after .bind() is called.

See Does bind() change the function reference? | How to set permanently?

So, to add or remove it, assign the reference to a variable:

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

This works as expected for me.

7
  • 4
    Excellent, this should be the accepted answer. Thanks for updating an old topic, this topic came up on search engine as number one hit and it didn't have a proper solution till you posted this now.
    – Blargh
    Commented Apr 16, 2014 at 19:24
  • 1
    This is no different from (and no better than) the method mentioned in the question. Commented Feb 12, 2016 at 2:07
  • 1
    @AlbertoAcuña Modern browsers use .addEventListener(type, listener) and .removeEventListener(type, listener) to add and remove events on an element. For both, you can pass the function reference described in the solution as the listener parameter, with "click" as the type. developer.mozilla.org/en-US/docs/Web/API/EventTarget/…
    – Ben
    Commented Feb 25, 2016 at 15:45
  • 1
    make sure that x listener is not being replaced (renewed) before calling subsequent addListener() or removeListener()s. Otherwise, you will lose reference to the previous listeners.
    – Aryo
    Commented Dec 13, 2021 at 9:25
  • 1
    @juancho Essentially the same; this.myListener.bind(this, arg1, arg2) will create a reference bound with those arguments.
    – Ben
    Commented Mar 10, 2023 at 16:42
58

For those who have this problem while registering/removing listener of React component to/from Flux store, add the lines below to the constructor of your 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() {
    // ...
  }
  
}

3
  • 10
    Nice trick, but what does React/Flux have to do with anything? Commented Feb 12, 2016 at 2:17
  • This appears to be the correct approach when adding and removing event listeners from different classes or prototype functions, which is I believe the connection with this also applying to React components/classes. You're binding it at a common (e.g., root) instance level.
    – Keith DC
    Commented Jan 21, 2018 at 2:31
  • 1
    this.onChange = this.onChange.bind(this) actually this is what I was looking for. The function bound on this for ever :)
    – Paweł
    Commented Mar 21, 2018 at 1:55
2

It doesn't matter whether you use a bound function or not; you remove it the same way as any other event handler. If your issue is that the bound version is its own unique function, you can either keep track of the bound versions, or use the removeEventListener signature that doesn't take a specific handler (although of course that will remove other event handlers of the same type).

(As a side note, addEventListener doesn't work in all browsers; you really should use a library like jQuery to do your event hook-ups in a cross-browser way for you. Also, jQuery has the concept of namespaced events, which allow you to bind to "click.foo"; when you want to remove the event you can tell jQuery "remove all foo events" without having to know the specific handler or removing other handlers.)

5
  • I'm aware of the IE issue. I'm developing an application that relies heavily on canvas so IE7- are out. IE8 supports canvas but at the minimum. IE9+ supports addEventListener. jQuery's Namespaced Events looks very neat. The only thing I'm worried about is efficiency.
    – takfuruya
    Commented Jul 19, 2012 at 17:37
  • The jQuery folks work very hard to keep their library performing well, so I wouldn't worry about that too much. However, given your strict browser requirements you might want to check out Zepto instead. It's sort of like a scaled-down version of jQuery that is faster but can't support older browsers (and has some other limits). Commented Jul 19, 2012 at 18:28
  • JQuery namespaced events are widely used and have virtually no performance concerns. Telling someone not to use a tool that will make their code easier and (arguably more importantly) easier to understand, would be horrible advice, especially if done so out of an irrational fear of JQuery and imaginary performance concerns. Commented Sep 3, 2013 at 17:25
  • 1
    Which signature would that be? The MDN page on removeEventListener shows that both of the first two arguments are required.
    – Coderer
    Commented Aug 2, 2018 at 12:27
  • My mistake. It's been years since I wrote that answer, but I must have been thinking of jQuery's off or unbind method. To remove all listeners on an element you have to keep track of them as they get added (which is something jQuery or other libraries can do for you). Commented Aug 2, 2018 at 22:01
1

jQuery solution:

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

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

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

We had this problem with a library we could not change. Office Fabric UI, which meant we could not change the way event handlers were added. The way we solved it was to overwrite the addEventListener on the EventTarget prototype.

This will add a new function on objects element.removeAllEventListers("click")

(original post: Remove Click handler from fabric dialog overlay)

        <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>
1

As others have said, bind creates a new function instance and thus the event listener cannot be removed unless it is recorded in some way.

For a more beautiful code style, you can make the method function a lazy getter so that it's automatically replaced with the bound version when accessed for the first time:

class MyClass {
  activate() {
    window.addEventListener('click', this.onClick);
  }

  deactivate() {
    window.removeEventListener('click', this.onClick);
  }

  get onClick() {
    const func = (event) => {
      console.log('click', event, this);
    };
    Object.defineProperty(this, 'onClick', {value: func});
    return func;
  }
}

If ES6 arrow function is not supported, use const func = (function(event){...}).bind(this) instead of const func = (event) => {...}.

Raichman Sergey's approach is also good, especially for classes. The advantage of this approach is that it's more self-complete and has no separated code other where. It also works for an object which doesn't have a constructor or initiator.

0

Here is the solution:

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);
-1

If you want to use 'onclick', as suggested above, you could try this:

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

I hope it helps.

-1

can use about 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
  • add/removeChangeListener is a method specific to React, not ES7 Commented Nov 23, 2020 at 21:21
-3

It's been awhile but MDN has a super explanation on this. That helped me more than the stuff here.

MDN :: EventTarget.addEventListener - The value of "this" within the handler

It gives a great alternative to the handleEvent function.

This is an example with and without bind:

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
}

A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:

Not the answer you're looking for? Browse other questions tagged or ask your own question.