2

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);
});
Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
Rick Stanley
  • 732
  • 2
  • 9
  • 23
  • *I do can unbind* ????? – Scott Marcus Nov 27 '18 at 02:12
  • it's because the declared `checkClick` function `!==` the function resulting from `checkClick(){}.bind(this)`, which means `removeEventListener()` fails to find a match – Dacre Denny Nov 27 '18 at 02:13
  • 1
    note that `Function.bind() !== Function.bind()`. Every call to `.bind` creates another object or function (your second example `checkClick.bind(this) !== checkClick`). In order to remove an event listener you MUST pass in the same object (function) and options used to create the event listener. – Jay Harris Nov 27 '18 at 02:26

2 Answers2

4

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);
});
Dacre Denny
  • 29,664
  • 5
  • 45
  • 65
1

It's because this is in a function that is nested within another function and the nested function doesn't have the same invocation context as the outer one. The first one works because you are caching the object that the outermost this is referencing and you are then able to correctly reference it in the inner function.

You can read more about the volatility of this here.

Scott Marcus
  • 64,069
  • 6
  • 49
  • 71