How Does this Work (in JavaScript)

2012-03-18 21:19

Many caveats clutter the JavaScript language. Some of them are quite hilarious and relatively harmless, but few can get really nasty and lead to insidious bugs. Today, I’m gonna talk about something from the second group: the semantics of this keyword in JavaScript.

Why’s this?

It is worth noting why JS has the this keyword at all. Normally, we would expect it only in those languages which also have the corresponding class keyword. That’s what C++, Java and C# have taught us: that this represents the current object of a class when used inside one of its methods. It only makes sense, then, to use this keyword in a class scope, denoted by the class keyword – both of which JavaScript doesn’t seem to have. So, why’s this even there?

The most likely reason is that JavaScript actually has something that resembles traditional classes – but it does so very poorly. And like pretty much everything in JS, it is written as a function:

  1. function Greeting(text) {
  2.     this.text = text
  3. }
  4. Greeting.prototype.greet = function(who) {
  5.     alert("Hello, " who + "! " + this.text);
  6. }
  7.  
  8. var greeting = new Greeting("Nice to meet you!");
  9. greeting.greet("Alice");

Here, the Greeting is technically a function and is defined as one, but semantically it works more like constructor for the Greeting “class”. As for this keyword, it refers to the object being created by such a constructor when invoked by new statement – another familiar construct, by the way. Additionally, this also appears inside greet method and does its expected job, allowing access to the text member of an object that the method was called upon.

So it would seem that everything with this keyword is actually fine and rather unsurprising. Have we maybe overlooked something here, looking only at half of the picture?…

Well yes, very much so. And not even a half but more like a quarter, with the remaining three parts being significantly less pretty – to say it mildly.

Four meanings of this

It might not have been immediately visible, but in the previous example the this keyword has been used in two different meanings. They just happen to blur into single semantic, thanks to our associations with other object-oriented languages. Incidentally, those associations were probably the main reason behind adding the whole this/new mechanism to JavaScript. Unfortunately, it doesn’t look quite as appealing when viewed in the context of language as a whole. Because at this wider perspective, this sucks big time.

For starters, here are the possible referents of this, i.e. things it can point to depending on where and how the keyword is used:

  • objects constructed using the new keyword, as seen in the Greeting function above
  • objects we’re calling methods on, as seen in the greet function above
  • the global object where all the global variables reside; for browsers, this would be the window object
  • any object at all

What you might have noticed is the massive drop of sanity level between the second and third option. There is absolutely no mistake here: this.x = 1; can mean the same as window.x = 1; and create a global variable, happily cluttering our global namespace. In fact, this is the default behavior of this when used inside a function. It’s the first two, non-crazy variants that require a special call syntax, while the following “just works”:

  1. function foo(arg) {
  2.     this.x = arg;
  3. }
  4.  
  5. foo(42);
  6. alert(window.x === 42); // true

Chances are, it doesn’t work the way we would expect it to work.

It’s all in the call

Time for a small brain teaser. Could you compare the Greeting and foo functions, and find some differences between them? Preferably, it should be a difference which is unrelated to naming and would persist even if we compiled minified both functions… Can you find at least one?

You could try for a while, but it would ultimately prove futile: there are none. Nothing is special about the so-called constructor of our Greeting “class”: it is an ordinary JavaScript function. While it seems to exhibit some class-like behavior, it is only due to our usage of new. If we called it directly, we would end up with the exact same behavior as with foo example above: this would bind to window and text would end as a global variable.

Not only this is surprising, confusing and error-prone, but it also generalizes to every case where this is used inside a function! The gruesome conclusion is that:

The meaning of this depends on how the function was called, regardless of how it was defined.

To realize how unpleasant consequences this may have, consider a simple object that has a “method”:

  1. var counter = {
  2.     inc: function() {
  3.         this.value = (this.value || 0) + 1;
  4.     },
  5. };

Let’s say that we want counter.inc to be called in response to some external event. In asynchronous, callback- and event-based environments – where almost all of the JavaScript code operates – this would be an extremely common occurrence. Setting aside any specific details, we can safely assume that our method will at some point end up in a variable. It will be deep inside the guts of a framework or library of choice, but in the end, it will be essentially equivalent to this line:

  1. var callback = counter.inc;

When the event in question actually happens, callback is of course invoked:

  1. callback();

And it is invoked like a normal function (rather than a method), so this acquires meaning specific to that invocation. Which means the global (window) object, not the counter object as we would expect… Yuck!

Fortunately, this can be worked around in a relatively straightforward way: by capturing counter object in a closure, therefore ensuring that it ends up as this inside its inc method:

  1. var callback = function() {
  2.     counter.inc();
  3. };

Just think, though: what’s the chance that someone notices the “superfluous” function later on and decides to “refactor” it out?… If you ever use this trick in practice, make sure you add some adequate comment.

this and that

What about the fourth point, stating that this can actually refer to anything at all? Again, it is only a matter of how the function gets called – and it so happens that there are some very special ways of calling a function. Those are the apply and call methods, where one can completely specify not only the argument list, but also the referent of this inside the function:

  1. var greeting = {};
  2. Greeting.apply(greeting, ["Have a nice day!"]);
  3. greeting.greet("Bob");

Don’t be mistaken: this is a very cool feature, especially when used appropriately (and judiciously) in some well-known libraries:

  1. var names = ["Charlie", "David", "Eva", "Fanny"];
  2. $.each(names, function() {
  3.     greeting.greet(this);
  4. });

Problems arise when we already have this in our outer function, because it will be inaccessible (“overwritten”) in the inner one. As a remedy, it is suggested to capture the outer this in a local variable that will come as part of inner function’s closure:

  1. Greeting.prototype.greetAll = function(who) {
  2.     var self = this;
  3.     $.each(who, function() {
  4.         alert("Hello " + this + "! " + self.text);
  5.     });
  6. };

Conventionally the variable is often called that, although self is also popular for somewhat obvious reasons :)

this doesn’t look pretty

As we can see, this keyword in JavaScript is not quite as simple as in many other languages. Care must be taken when calling any function that uses it internally; otherwise we might end up with totally unintended results.

Be Sociable, Share!
Be Sociable, Share!
Tags: , , , ,
Author: Xion, posted under Programming »


2 comments for post “How Does this Work (in JavaScript)”.
  1. Kos:
    March 20th, 2012 o 11:11

    This article needs to “use” more “strict”!

    function foo(arg) {
    return this;
    }
    foo() === window;

    But:


    "use strict";
    function foo(arg) {
    return this;
    }
    foo() === undefined;

  2. Mariusz:
    March 24th, 2012 o 12:52

    This is a very useful passage /again on xion.log/
    Did you ever think of publishing a guide/course similar to your C++ megatutorial, but this time commercially and in English? I believe you would stand a firm competition to Wrox and apress, as quick examples…
    Anyway thanks for the C++ work and for these brief microtutorials herein:-)

Comments are disabled.
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.