JavaScript Context, Call and Bind – Ninja Level

December 5th, 2011 by Mike Wilcox

In my previous article I showed the differences between scope and context, basic problems that arise and how to fix them. If you are just using some JavaScript and maybe jQuery, an understanding of scope is all that is needed to get you by. Once you start using objects or namespaces however, you’ll start to run into issues with context and will need to use the keyword this. But when you get into object oriented JavaScript, you’ll need an advanced understanding of context and how to make it work for you. To do this, we’ll use the call() and apply() methods, and then a backwards compatible version of the new feature Mozilla recently released in JavaScript 1.8.5 called bind().
JavaScript Function Context Bind

Who the Hell Would Ever Use this F@#ing Language Anyway?

The title of this section is dedicated to a one-time Club AJAX attendee who looked at the next example and was so frustrated at how little he understood it, he started blurting out obscenities. Needless to say, he is now sentenced to life, doing Java.

So, to paraphrase Jack Nicholson… wait until you get a load of this:


Example #1 – Advanced Broken Context

“Waitaminute!” I hear you say. “I applied the context to the function, and onTimeout fired — why didn’t this.x resolve? And who the hell would ever use this language, anyway?”

I empathize, really. But remember, you don’t know how to paint without knowing about the color red, and you can’t really say you know JavaScript unless you know about context and all of its pitfalls. When you are able to understand this concept, it will elevate you from just a guy who writes some JavaScript to outright NINJA!

Before we can decipher why the previous example doesn’t work, let’s change it up so we can better break down the issues.


Example #2 – Advanced Broken Context Variation

Example #2 basically substitutes otherMethod for setTimeout, so we can side step that particular nuance for now. This example shows a similar situation: we are passing a method to another method and trying to invoke it. However, this.x is still undefined. The reason for this is kind of tricky.

Yes, we used this with onTimeout. But we only used this in order to find onTimeout, we didn’t use it to invoke onTimeout. If we didn’t use this, we would get an error saying the function onTimeout is not found, which makes sense, because that would indicate we want a function (as in, var onTimeout();), but what we really want is a method. So we use this, to get the method, and pass it to this.otherMethod.

Refresher: Methods are associated to objects like: object.myMethod = function(){}; while functions are not: var myFunc = function(){}.

The Reason

Ironically, if m() or otherMethod() invoked this.onTimeout, it would work. But when we retrieved the method we sort of “extracted it” from the object, and passed it as a variable, of a function if you will. It’s no longer a method, it’s just a function with no call object bound to it. So we need to re-bind this, which we can do with the call() method:


Example #3 – Advanced Broken Context Variation Fixed
NOTE: The ECMAScript documentation refers to a method’s bound object as its activation object. David Flanagan, author of JavaScript: The Definitive Guide refers to it as a call object, which I like, because you can assign context with a function’s call method.

Is “this” Broken?

We fixed it, but I sense dissatisfaction in you, young Skywalker. “Why should I jump through these hoops? This language is broken!” I agree that this aspect is at the least confusing and seems just plain wrong. But there is a reason for this behavior, believe it or not. Remember, JavaScript functions are just data and can be passed around as such.

Okay, if you don’t believe me, perhaps you’ll believe Brendan Eich from this Talk:

[The reason for the keyword "this"] is I wanted functions, which are the main idea in JavaScript, to also be methods; and for them to be methods, they had to have a receiving object who’s property was the value of that function.

That explanation was a bit… recursive. Perhaps a simple exercise will help. In Example #4, one object copies a method from another. What should be logged, 10 or 20?


Example #4 – Copied Method

If you answered 20, you’d be correct. Even though o.m was copied as data, it was still invoked on the last line with the o2 object, which set its context. But you may see what the issue would be if a copied method carried its context around. Then the answer would be 10, derived from the original object. That would make JavaScript look even more broken.

The following is another example of the use of call(). You can borrow a method from one object and apply it to another object temporarily:


Example #5 – Borrowing a Method from another Object

In Example #5, o.changeProps(1); invokes as normal, but the last line, o.changeProps.call(o2, 1); calls that same method, yet invokes it within a different object. Admittedly, this example is a bit contrived and is probably making you squirm from all the best practices it violates. But it does show the versatility of the language and exactly how call works.

Note: The first argument of call() should be an activation object and all arguments after that will be passed to the method. apply() works just like call() except the second argument should be an array and is often used to pass the arguments object.

Now that we have a better understanding of context and how to fix it, we can readdress the problem in Example #1. However, we can’t use call() to fix the context, because that would have to happen within the setTimeout function and we can’t change native code. Well, we could, but that would be like trying to waterproof a screen with a toothpick. Because the same problem will arise again and again, in callbacks, connections, and myriad other places. We have a better way.

Bind

The problem can be fixed with the new bind method in JavaScript.1.8.5. And in spite of the complexity of the problem, the solution is really quite simple:


Example #6 – Advanced Broken Context Fixed

Now that this is bound this.setTimeout, we can pass it around and it won’t lose its context. And that’s it! Fixed! Good night nurse!

What’s that? Does this work in what other browser? Hm, I promised some sort of ninja status, didn’t I? That would necessitate some sort of explanation.

Bind Under the Hood

First of all, no, this does not work in that browser, but we can home brew a utility method that does, which we will add it to the prototype object of the Function constructor. Ever added the forEach method to Arrays? We’re doing the same thing here except with the Function’s prototype.

What we need to do is take our existing function and the context passed, and create a newly bound function. We are not going to return the bound function directly because if we did, it would fire immediately, which we don’t want. We are passing the bound function as data to be invoked later. Therefore we need to return another function that has the bound function inside it.


Example #7 – Simple Bind Prototype

You’ll notice that the function is bound with apply() instead of call(). This is because our utility code has no idea how many arguments may be passed, and apply() handles a variable amount… of variables.

Include the Simple Bind Prototype early within your code, and Example #6 will work — in all browsers.

But Extending Native Objects is Bad

Oh, so you’re one of those purists who believes you shouldn’t mess with native objects eh? That’s okay, I’m concerned about that too. The tide is shifting a bit in the JavaScript library developers community that it’s okay to extend native objects (not host objects) — as long as those extensions are to fix gaps in weaker browsers with standardized functionality. And of course if it’s your code, you can do whatever you want!

And… what you want is to not extend the native object? That’s actually a good thing here, because the wrapper-implementation is a little more clear in how it works as there isn’t the confusion of what “this” is, as in Example #7.


Example #8 – Simple Bind

If using the wrapper implementation, you would change the code from setTimeout(this.onTimeout.bind(this), 1);
to:
setTimeout(bind(this.onTimeout, this), 1);

The bind method passes the argument object, which is quite handy on callbacks. The following example is a recreation of a callback mechanism, which demonstrates how variables are passed:


Example #9 – Passing Variables

The Mozilla Implementation

You may have taken a peak at the Mozilla bind page, which would be only natural since I linked to it. I didn’t recreate their functionality because it’s a bit complex. Our solution handles 95% of what you would ever do with bind(), while theirs handles new concepts like binding to instance constructors and Arrays. But feel free to experiment!

Conclusion

Congratulations. If you made it this far into the blog and haven’t reached for a bottle of aspirin, bottle of alcohol, or a noose, that means you’ve managed to absorb one of the hardest concepts in JavaScript. Context is the key differentiator between JavaScript and all the other major languages.

Tags: , , , , , , , , , , ,

2 Responses to “JavaScript Context, Call and Bind – Ninja Level”

  1. Bojan says:

    Hi Mike,
    Very nice that you took the time to explain the context once again. I think that that is probably the hardest thing to grasp about JS.

    Just a couple of things…

    You mention “Ever added the forEach method to Arrays?”, but this is a part of JavaScript since version 1.6 (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach). I happen to know because I recently wrote a post about that :) (http://www.bjelic.net/2011/11/16/coding/evolution-of-javascript-loop/)

    Another thing is that you probably meant to write “Context is the key differentiator between JavaScript and all OTHER major languages.” ;)

    Cool article!
    Cheers,
    Bojan

  2. Mike Wilcox says:

    Good post Bojan – I Tweeted it! And added ‘other’ which was a very good suggestion :)