Thursday, May 6, 2010

Javascript and 'this' keyword

One of the most confusing things about javascript is function scope the this keyword. I mostly use it for setting fields and accessing member functions from within my javascript class. Depending on how you call the methods, this be something you wouldn't expect.

For those of you who don't know what the this keyword is, it always points back to the object that owns the function your in. Most problems come up when you think your getting the owning object, but instead your getting the current window object. Huh? Taking the example in this article, notice the different behaviors depending on how the methods are being called.
function  foo()
{
    var privateVal = "Private Val";
    this.publicVal = "Public Val";

    var privateAlert = function(str)
    {
        alert(str + this.publicVal);
        alert(str + privateVal);
    }

    this.Run = function()
    {
        privateAlert("Private Call: ");

        this.publicAlert = privateAlert;
        this.publicAlert("Public Call: ");
    }
}

var bar = new foo();
bar.Run();
And here is the output from that:
Private Call: undefined
Private Call: Private Val
Public Call: Public Val
Public Call: Private Val
The interesting behavior is that we're calling the same function, but in two different ways. When calling the function from the this keyword, the interpreter is correctly setting this inside the scope of the function. When calling without the this keyword, it uses the global (window) scope. The reasoning behind this can be found in the javascript spec, or you could take a look at the above referenced article. In short, if you call a function without a preceding object (i.e. this.aFunction(), or MyClass.aFunction()), then this gets defaulted back to window.

So what are you suppose to do? The article mention above proposes a solution to create a private class field call me that is initialized to point to this. The object that this points to at the time me is initialized will what you would expect. That way you can always use me instead of this throughout your class implementation. That's definitely a simple solution, but my VB nightmares scare me away from anything that uses me. One of the ways in which I get around this issue is by making use of the bind method from Prototype.
Function.prototype.bind = function(obj) 
{ 
    var method = this, 
    temp = function() 
        { 
            return method.apply(obj, arguments); 
        }; 
  
    return temp; 
 } 

This function wraps a new function around the calling function while binding that calling function to the obj parameter. Huh? This explains it pretty well. Let me show you how we can use it keep the value of this consistent with what we would expect.
function  foo()
{
    var privateVal = "Private Val";
    this.publicVal = "Public Val";

    var privateAlert = (function(str)
    {
        alert(str + this.publicVal);
        alert(str + privateVal);
    }).bind(this);

    this.Run = function()
    {
        privateAlert("Private Call: ");

        this.publicAlert = privateAlert;
        this.publicAlert("Public Call: ");
    }
}

var bar = new foo();
bar.Run();
Notice that I've added the bind call to the private function so that any references to this inside that private function will be reference the expected object. If you run the above code, this is the output you would now get.
Private Call: Public Val
Private Call: Private Val
Public Call: Public Val
Public Call: Private Val
That's more inline with what we would expect!

2 comments:

  1. super-hero like.

    Very interesting.

    ReplyDelete
  2. I can't say I'm very fond of languages which are so nebulous in their definitions of their "this" keyword. I got confused a few times by "this" in JavaScript but for lack of understanding at the time I decided to just stay away from it until I had a clear use for it.

    In Lua I ran the same issue with "self" which, IIRC, can be passed implicitly as the first argument of a method depending on how you call that method. This implies of course that you can pass whatever you want as that first parameter, which then leads to more issues.

    ReplyDelete