JavaScript's Slippery this Reference and Other Monsters in The Closet

Wednesday, July 4, 2007

One problem in WWWTC # 16 is the assumptions Estelle makes about this. In C# you don’t need to explicitly use the this keyword to reference an instance field from inside an instance method. It’s tempting to write a method like Estelle’s …

InputManager.prototype.resetToInitialValue = function()
{
    _input.value = _initialValue;
}

… only to find out the method tries to access a global variable named _input and a global variable named _initialValue. In ECMAScript we need to explicitly bring this into play to reference entries in the object’s own dictionary:

InputManager.prototype.resetToInitialValue = function()
{
    
with(this)
    {    
        _input.value = _initialValue;
    }    
    
// OR
    this._input.value = this._initialValue;
}

Thanks to delegate inference in C#, it’s also tempting to think we can assign an instance method to a DOM event …

function InputManager(input)
{
    
this._input = input;
    
this._initialValue = input.value;
    
    
// won't work
    this._input.ondblclick = this.resetToInitialValue;
}

… only to find out we’ve simply given the DOM event a reference to a function object. The DOM element will happily invoke the function directly and without using an instance of an InputManager object. Our this reference inside of the resetToInitalValue function is then useless. Similar code in C# will work, because the C# compiler will magically create a delegate behind the scenes, and that delegate will manage an object reference, and invoke an instance method through that object reference.

There are mechanisms in ASP.NET AJAX to fix the above scenario, but in the end it boils down to giving the DOM element a function object that can remember the proper this reference. A simple approach would look like:

function createDelegate(object, method)
{
    
return function() { method.apply(object, arguments); }
}

function InputManager(input)
{
    
this._input = input;
    
this._initialValue = input.value;
    
this._input.ondblclick = createDelegate(this, this.resetToInitialValue);
}

Here we are using the magic of a JavaScript closure to create and return a new function object from inside createDelegate. The DOM event will reference this new function object. When this new function object is invoked by the DOM element, the new function sets up a proper invocation of resetToInitialValue by using Function.apply and passing through the InputManager reference to use as the this reference.

We’ll explain this topic of closures and Function.apply more thoroughly in the future.

Those fixes should get the code working for now, but what else in that code was a monster in the closet?

For one, the event handler registration could use some work. There are tons of JavaScript code and articles written on the topic of how to do safe, non-desctuctive, cross-browser event registrations, so for now I’ll just say if you are using something like ASP.NET AJAX’s $addHandler technique your code will improve over the above approach.

Secondly, it’s good practice to clean up event registrations. The code sets up a scenario where each InputManager holds a reference to DOM element (in the _input field), and that same DOM input holds a reference back to it’s InputManager (via the function object / event handler that formed a closure over the InputManager instance). In the past, these circular references created memory leaks and were a common “memory leak pattern” in Internet Explorer. I say “in the past” because these problems are fixed in IE7 (and in IE6 (with an update)). Still, the generally accepted practice in today’s AJAX patterns is to clean up afterwards, which feels like the right thing to do as not all browsers are perfect in figuring out these circular references.


Comments
Rick Strahl Wednesday, July 4, 2007
Yup, this one topic that bugs me endlessly about JavaScript because it's so incredibly unintuitive. The way closures and scoping work is flexible but damn if it's logical in any way.

I mentioned this a while back but it really has gotten to the point if you want to do anything useful these days with JavaScript you need to have some sort of framework in place to deal with these idiosyncracies.

It still feels like a royal hack. Fakign OOP with JavaScript is like lipstick on a pig <s>... but we better get used to it for the foreseeable future.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!