The invoking object contains the call site, and the call site determines the ‘this’ binding.
Now, as promised, we are back to cover the worst case ‘this’ scenario callbacks, or how to find the ‘this’ binding for a function that is passed as an argument into another function. Clutch your head and weep no more, because there are tricks to be learned — both for identifying callback ‘this’ and, if all else fails, how to just go ahead and force an explicit ‘this’ binding.
Let’s take a quick spin through how callbacks work. A higher order function is a function that accepts another function as an argument. A callback function is a lucky function that gets passed into the enclosing higher-order function:
The callback function gets executed (called) inside the higher order function, but not necessarily immediately. It gets “called back” — hence the name — at whatever point serves the code’s purpose.
Getting REALLY Tricky: ‘this’ in Callback Functions
Callback functions are at the mercy of whichever higher order function they are passed into.
In other words, to track down what a callback’s ‘this’ is pointing to, you need to inspect the higher order function enclosing it.
This works because, in a callback invoked by an enclosing function, the ‘this’ context changes. The value ‘this’ holds is reassigned to the function that is calling the function — i.e., there’s your call site, which holds your ‘this’:
You can identify ‘this’ by looking at how the outer function, higherOrder(), works: it is defined in global scope, so its ‘this’ will point to the Window object as its invoking object.
This is a situation where the left of the dot rule applies. Inside a new function, callbackAsMethod(), we create a new object, oddballObject, with a ‘name’ property of ‘don’t ever actually do this.’ We then add a new property to the oddballObject called ‘callback’ and set it equal to the callback function passed in as argument. Then we run the callback as a method, using the same the same callback function, getThis(), passed into callbackAsMethod() — so ‘this’ inside of the callback will be whatever is left of the dot operator, or the invoking object:
You can’t tell what ‘this’ will be by looking at the callback function — getThis() after all is scoped to Window. You need to look at how getThis() is being called by the higher order function, in this case, an invoking object that calls that callback as a method.
Callbacks inside constructor functions are, thankfully, very straightforward: You call the callback as a constructor, and ‘this’ will point to the newly constructed object. Remember that the ‘new’ keyword is the identifier for a constructor function, and the giveaway for where ‘this’ will point:
Recall our key takeaway (and really, if you can’t cite this from memory by now, it’s time to consider tattooing it on your arm or something):
The invoking object contains the call site, and the call site determines the ‘this’ binding. Where a function or method is defined has nothing to do with where its ‘this’ is pointing.
Even though callback functions add an additional layer of complexity that requires a bit more of a think-through, at the core they aren’t any different from a regular function.
“I GIVE UP!!!” Or, Controlling ‘this’
Hearken back to our default getThis() function, where ‘this’ points to window:
What if we wanted to control where ‘this’ points and change the value of window to something else? This is where bind(), call() and apply() come in handy. All three are built-in methods on functions.
Invoking bind() on any function returns a copy of the function where ‘this’ is set to the first argument passed into bind. Which is how you get to determine what, exactly, ‘this’ is:
Again: note how bind() does not invoke the function; it returns a copy of the function, which is why when you use bind() the new function it first needs to be assigned to a variable, and then called.
In our example, bindThis() returns the copy, with ‘this’ set to the new object (“saying: ‘Such bind!”). So even though bindThis() was created from the original getThis() function — whose own ‘this’ remains Window — we were able to explicitly set ‘this’ for bindThis() to something different, of our own choosing, even though bindThis() is calling the global getThis() function.
Similarly, we can use apply() and call() to change the ‘this’ value inside of getThis().
As with bind(), we pass our desired ‘this’ value as an argument. The difference is apply() and call() will both change the ‘this’ value inside of a function and then run it immediately. With both, the first argument your is chosen ‘this’ binding. In our default function but with arguments added, ‘this’ still points to Window:
If you don’t pass any arguments into the function, apply() and call() behave exactly the same way. But in a function that accepts args, apply() and call() are slightly different. Let’s modify our getThis() function so it accepts two arguments. One is a name, and the other a saying. With apply() and call(), we take our default function getThisWithArgs() and first pass in the chosen ‘this’ value, and then second pass in our arguments.
For apply(), the arguments are passed as an array; in call() arguments are passed in one by one. This is a very small difference and one that should be pretty clear when the times comes to actually use apply() vs. call().
Bind() = Getting Bound up Permanently
It’s important to bear in mind that once bind() sets a chosen ‘this’ value, it’s immutable. That is, a function returned from bind() can never again be bound to a different ‘this’ value. Functions can only be bound once. Choose wisely, grasshopper.
The Grand Finale: Callbacks with bind()
Happily, identifying ‘this’ for callback functions works the same way when you want to set exactly what ‘this’ will be using bind(), apply() or call(). Given that they work pretty much the same way, with slight variations, we will just do a quick bind() demo. (Flashback: the important difference to keep in mind is that apply() and call() both set the ‘this’ value inside a function and then run it immediately. Bind() returns a copy of the function without running it, so you must explicitly call it).
So here we have our higher-order function, callbackBindToDoge(), accepting a callback and using bind to set the callback’s ‘this’ to a specific value. We should expect an object that comes back that has a name property equal to Doge:
As in previous examples with callback functions, only looking at getThis as the callback argument, you have no idea what ‘this’ is going to be. But once you track back to, and look inside, the invoking function, callbackBindToDoge(), it becomes evident where ‘this’ will be set. Because we have bind() like a big red arrow saying “‘this’ HERE!”
One final wrinkle: recall from the section above that a function returned from bind can never be bound to anything else. Let’s see what happens when we pass callbackBindToDoge() an already bound callback function. It will attempt to carry out its job of binding any passed callback to the original/internal doge object, “Such callback!” — but it’s going fail:
Just try to remember this single upshot: callback functions are completely at the power of the higher order function calling them. That outer enclosing function is the call site.
Inspect the outer function, see what’s going on there, and you can track down a callback’s ‘this’ — no matter where that slippery little sucker is trying to hide.