Putting Function.apply() to Work

Friday, July 6, 2007

In the last post I demonstrated how you can fiddle with the this reference during a method invocation using Function.call and Function.apply. After reading that post, you should understand why Function.apply was the most interesting method to us. It's because we can use apply() to specify the this reference for an arbitrary target method, and also pass-through an argument list to that target method via the arguments identifier.

With the above understanding in place, let's look at the following code.

<input type="button" id="submitButton" value="Press Me!" />

<
script type="text/javascript">

    function MagicButtonManager(buttonId, message)
    {
        
this._message = message;
        
var btn = document.getElementById(buttonId);
        
        btn.onclick =
// what do we here do here?
    }
    
    MagicButtonManager.prototype.showMessage =
function()
    {
        alert(
this._message);
    }

    
var m = new MagicButtonManager('submitButton', 'Hello World!');

</script>

How do we get the onclick event to invoke our showMessage() method using the correct this reference? We've already seen this approach doesn't work:

btn.onclick = this.showMessage;

Inside of showMessage(), the this reference will point to the submit button instead of a MagicButtonManager instance, and this._message will be undefined. We've assigned a function object to the onclick event. The button does not invoke that function through an instance of the MagicButtonManger (which would correctly setup the this reference) - it just directly invokes that function.

What we need to do is trick the button into calling showMessage via our instance of the MagicButtonManager, right? As in: manager.showMessage();. Unfortunately, the only thing the event handler knows how to do is invoke a function. It doesn't know how, given an object, to "dot operate" (did I just invent a verb?) and invoke a specific method on that object. Here then, is what we know so far:

  • We need to give the button a function object to invoke.
  • To setup the correct this reference for showMessage(), we don't necessarily need to invoke showMessage via a MagicButtonManager instance - we can also use Function.apply().

What our createDelegate function was doing in a previous post (and what the ASP.NET AJAX Function.createDelegate method essentially does), is this:

function createDelegate(object, method)
{
    
var shim = function()
                {
                    method.apply(object, arguments);
                }

    
return shim;
}

// ...
btn.onclick = createDelegate(this, this.showMessage);

Here we are creating a new function object, a shim if you will. When this shim is invoked, it will use apply() on the incoming method parameter, which invokes that specified method. The shim will pass object as the this reference for apply to setup, and the shim does a "pass-through" of all its incoming arguments using the arguments identifier.

The createDelegate function returns a shim as a result. We can assign the return value of createDelegate to an event, so the event can invoke the shim function. When the event fires and shim executes, our this reference is correct thanks to the magic of apply.

Everything works now. Thank you, and goodnight.

What's that? Something doesn't make sense?

We've covered apply and arguments in great detail, so I assume if you are sticking around you might be puzzled by the shim function. How does shim remember the method and object parameters? Since method and object are arguments to createDelegate - don't they go out of scope when createDelegate exits on the closing curly brace? Aren't those function arguments blown away with the crackling pop of a stack frame? Wouldn't those arguments be long gone by the time the button event executes the shim function?

Hang on for one more post...


Comments
gravatar Simon Thursday, February 25, 2010
It's nearly 3 years since you wrote this, however, this may be of use to someone:

'this' is a slippery beast. We can use closures to capture snapshots of variables, and this includes 'this'. However 'this' is also a keyword and is therefore slightly awkward to capture in a closure, unless you know what you're doing. Douglas Crockford wrote that the 'failure' of JS in not capturing 'this' in a closure is bad. I disagree. Using his 'work-around' makes the code much easier. Just remember that 'this' is also a keyword, and willk point at the global object unless
1. you're in a function that's been instantiated via 'new' (or in it's prototype chain)
2. You're in function that's been invoked via apply/call
3. You're in a function that's been invoked from a specific object in the (X)HTML DOM. Can 3 be thought of as a sub-type of 2?

Anyway to capture 'this', try:

function MagicButtonManager(buttonId, message){
this._message = message;
var btn = document.getElementById(buttonId);
var that = this;
btn.onclick = function(){that.showMessage2();};
}

MagicButtonManager.prototype.showMessage2 = function() {
alert(this._message);
alert(message);
}

var message= "Hello World!";
var m = new MagicButtonManager('submitButton', message);
var message= "I'm not talking to you!";
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!