Getting “this” right

There are still a few topics left for just covering language features in this series about cross-compiling ActionScript to JavaScript . The semantics of the JavaScript keyword this are so tricky that I would like to discuss it in this separate post. Getting this right is hard.

 

Object and this

Douglas Crockford offers an excellent description of the JavaScript keyword this:

A function is an object. It can contain members just as other objects. This allows a function to contain its own data tables. It also allows an object to act as a class, containing a constructor and a set of related methods.

A function can be a member of an object. When a function is a member of an object, it is called a method. There is a special variable, called this that is set to the object when a method of the object is called.

For example, in the expression foo.bar(), the this variable is set to the object foo as a sort of extra argument for the function bar. The function bar can then refer to this to access the object of interest.

In a deeper expression like do.re.mi.fa(), the this variable is set to the object do.re.mi, not to the object do. In a simple function call, this is set to the Global Object (aka window), which is not very useful. The correct behavior should have been to preserve the current value of this, particularly when calling inner functions.

If you prefer a shorter description you will probably like this note from Google’s JavaScript Style Guide:

The semantics of this can be tricky. At times it refers to the global object (in most places), the scope of the caller (in eval), a node in the DOM tree (when attached using an event handler HTML attribute), a newly created object (in a constructor), or some other object (if function was call()ed or apply()ed).

You might think: “Yes, this is a little bit complicated. But why should that be a problem? Doesn’t ActionScript handle this the same way JavaScript does?”. Herein lies the problem. ActionScript’s semantics of this are slightly different.

 

Injecting this explicitly

Let’s start with an easy example:

// ActionScript:
public class Greeter
{
    private const m_hello : String = "Hello";
    private const m_world : String = "World";
    public function concatenate( s1 : String, s2 : String ) : String
    {
        return s1 + ", " + s2;
    }
    public function greet() : void
    {
        trace( concatenate( m_hello, m_world ) );
    }
}
var greeter : Greeter = new Greeter();
greeter.greet();

I admit, this example seems clumsy and doesn’t do much. The interesting part is the generated JavaScript, because ActionScript does not require you to specify this when accessing instance members or instance methods while JavaScript does require this:

// JavaScript:
var Greeter = function() {};
Greeter.prototype.m_hello = "Hello";
Greeter.prototype.m_world = "World";
Greeter.prototype.concatenate = function(s1, s2)
{
     return s1 + ", " + s2;
}
Greeter.prototype.greet = function()
{
    var self = this;
    trace( self.concatenate( self.m_hello, self.m_world ) );
}
var greeter = new Greeter();
greeter.greet();

I highlighted the significant portion of this transformation. Our cross-compiler needs to resolve identifiers like concatenate ,  m_hello, and  m_world for injecting this when accessing instance members and instance methods. Please note that in this example I am introducing the concept of assigning this to a local variable self and using self in the body. I will explain later why I am doing that.

Just to emphasize the importance of resolving identifiers at compile time let me modify the example above to using static members instead of instance members (I am only showing the differences):

// ActionScript:
private static const m_hello : String = "Hello";
private static const m_world : String = "World";

That would affect the transformation in greet() as follows:

// JavaScript:
Greeter.m_hello = "Hello"; 
Greeter.m_world = "World";
trace( self.concatenate( Greeter.m_hello, Greeter.m_world ) );

 

Glueing this to an instance method

As an extension to our Greeter example above let’s pass a function as a parameter to another function:

// ActionScript:
public class Greeter
{
    public function concatenate( s1 : String, s2 : String ) : String
    {
        return s1 + ", " + s2;
    }
    public function greet(concatFunc : Function) : void
    {
        trace( concatFunc( "Hello", "World" ) );
    }
}
function callFunction( greetFunc : Function ) : void {  greetFunc(); }
var greeter = new Greeter();
callFunction( greeter.greet );

In theory this modification shouldn’t change much. Instead of calling  greeter.greet()directly we now let callFunction do the job. The generated JavaScript should be straight forward. But surprisingly this version would be incorrect:

// JavaScript:
var Greeter = function() {};
Greeter.prototype.concatenate = function(s1, s2)
{
     return s1 + ", " + s2;
}
Greeter.prototype.greet = function()
{
    var self = this;
    trace( self.concatenate( "Hello", "World" ) );
}
function callFunction( greetFunc )
{
    greetFunc();
}
var greeter = new Greeter();
callFunction( greeter.greet ); // THIS IS INCORRECT!

When you run this code in the browser (after replacing trace with console.info) you’ll get this error:

TypeError: Result of expression 'self.concatenate' [undefined] is not a function.

Why? It turns out that this in  Greeter.greet changes depending on who is calling it. This is very counter intuitive but this in  Greeter.greet is in this example DOMWindow and not  Greeter. After adding our own concatenate to the outer scope, which belongs to DOMWindow, everything works fine again:

//JavaScript:
function callFunction( greetFunc )
{
    greetFunc();
}
var concatenate = function(s1, s2) { return "Global: " + s1 + ", " + s2; }
var greeter = new Greeter();
callFunction( greeter.greet );

But that’s not really what we want. We have to preserve the internal consistency of the source code in the generated code of the target, which means that we need this in  Greeter.greet always to be an instance of Greeter and never the caller.  Fortunately this is a well known problem and most JavaScript frameworks offer “glueing” or “binding” an instance method to its instance. Google’s base.js has bind() and jQuery provides proxy(). For our example let’s just use jQuery’s proxy(). The correct transformation then becomes:

var greeter = new Greeter();
callFunction( jQuery.proxy(greeter.greet, greeter) );

In conclusion, we learned that our cross-compiler needs to inject proxy calls for instance methods when passed as function parameters.

 

Anonymous functions

If you throw anonymous functions into the mix things get really messy. This is a modified version of one of FalconJS’s unit tests (credits go to Peter Flynn):

// ActionScript:
public class WhatIsThis
{
    private var instVar:String = "I";
public function run() : void
{
    var outside:int = 5;
    var f : Function = function():void
    {
        this.foo = 6;
        trace(this.foo);     // Expected: 6
	this.outside = 7;
	trace(this.outside); // Expected: 7
	trace(outside);	     // Expected: 5

	trace(instVar);      // Expected: I
     };
         // used as a constructor: 6,7,5,I
         var obj:Object = new f();
         // called directly: 6,7,5,I
         f();
    }
}
var whatIsThis = new WhatIsThis();
whatIsThis.run();

What makes this unit test so tough is that we use this all over the place. Inside the anonymous function we set this.outside to 7, where this refers to f. Then we use outside without this referring to a variable defined within run, which is the outer scope of f. Finally we use instVar , which we expect to be resolved to WhatIsThis.instVar, because instVar is an instance member.

Here is how you could cross-compile that mess to JavaScript:

//JavaScript:
var WhatIsThis = function() {};
WhatIsThis.prototype.instVar = "I";

WhatIsThis.prototype.run = function()
{
    var self = this;
    var outside = 5;
    var f = function()
    {
        this.foo = 6;
        trace(this.foo);
        this.outside = 7;
        trace(this.outside);
        trace(outside);
        trace(self.instVar);
    };
    var obj = new f();
    f();
}    

var whatIsThis = new WhatIsThis();
whatIsThis.run();

I encourage you to run this code in the browser. You will get 6,7,5,I twice as you would get with the original ActionScript code. You can perhaps also now see why it is a good idea to use self in instance methods instead of this. Within anonymous functions we need to be able to refer to the instance the functions are part of. We cannot just use this , because that would refer to the anonymous function. Hence self. But in order to make that work we have to also establish that anonymous functions never declare self. This all sounds more difficult than it really is. In the end everything magically works.

 

Conclusion

Getting this right is hard. The semantics of JavaScript’s this are tricky and get even trickier when cross-compiling ActionScript to JavaScript, because ActionScript handles this slightly differently.

  • In contrast to JavaScript, ActionScript does not require you to reference instance members and instance methods explicitly with this.
  • Instance methods have to be “glued” to their instances when used as function parameters, otherwise this would change dependent on the caller.
  • Within method bodies we declare and use self instead of this  so we can always reference the instance even from code within anonymous functions.
  • Anonymous functions should not declare self and any usage of this within the body of an anonymous function shall not be modified.

Congratulations! You just made it through the description of one of the most difficult problems when cross-compiling ActionScript to JavaScript.