Search code examples
javascriptjavascript-objects

Javascript: Bind/unbind function behavior


When I bind a function with the parent this passed in thisArg, I can't unbind the same named function expression, but without it I can unbind the function itself. Why is that?

This works:

choicesList.addEventListener("click", function() {

    const self= this;

    document.addEventListener("click", function checkClick(e) {

        if (!e) e = event;
        if (!self.contains(e.target)) {

            document.removeEventListener("click", checkClick);
        }
    }, false);
});

This doesn't:

choicesList.addEventListener("click", function() {

    document.addEventListener("click", function checkClick(e) {

        if (!e) e = event;
        if (!this.contains(e.target)) {
            document.removeEventListener("click", checkClick);
        }
    }.bind(this), false);
});

Solution

  • The reason for this is issue is that calling bind() on a function returns a new instance of that function:

    function someHandler() {
      alert('hi');
    }
    
    const someHandlerBinded = someHandler.bind(document);
    
    
    // Returns false, seeing as these are different instances of the function
    console.log( someHandlerBinded === someHandler );

    By setting an event handler directly, via the result of bind() as you are in your second block of code, this causes a new instance of that function handler to be passed to addEventListener(). This in turn means that the subsequent attempt to removing this handler on line:

    document.removeEventListener("click", checkClick);
    

    will fail, seeing that the the defined function checkClick is not the same as the actual handler function used for that click event (ie the new function instance returned from function checkClick(){ ... }.bind())

    One way to resolve this might be the following:

    choicesList.addEventListener("click", function() {
    
        // Declare the bound version of the click handler
        const boundClickHandler = function checkClick(e) {
    
            if (!e) e = event;
    
            if (!this.contains(e.target)) {
    
                // Removing the result of bind, rather than the declared 
                // checkClick handler
                document.removeEventListener("click", boundClickHandler);
            }
        }.bind(this)
    
        // Adding the result of bind as you currently are doing
        document.addEventListener("click", boundClickHandler, false);
    });