Tuesday, May 17, 2011

javascript callback function scope

Let's check a simplified normal callback example:(to make things simple, i ignore all necessary type checks or validations before calling callback)

callbackTest = function(callback) {
    //do some tasks
    //and then callback
    callback();
};

callMe = function() {
   alert('henry');
};

//this will alert 'henry'
callbackTest(callMe);


The example works fine.  But what if the callback function is a method of an object? Let's modify the example a little bit:

callbackTest = function(callback) {
    //do some tasks
    //and then callback
    callback();
};

person = {
    name : 'henry',

    callMe : function() {
        alert(this.name);
    }
};

//we try to pass person.callMe as a callback function
callbackTest(person.callMe);

If you run the example, you will find the result is not as you expected. The reason is, the callback method uses 'this' to refer to the object it belongs to and it will cause unexpected behavior.
When we use callback to invoke person.callMe in callbackTest function, this.name is actually not defined, so this will refer to the global object. As you can imagine, if we define this.name in callbackTest function, the callback will actually alert the name defined in callbackTest instead of the person object.

So how to fix this issue? The solution is we need to pass the callback function and the object the callback belongs to. Check this code:

function callbackTest(callback, object)
{
     callback.call(object);
};

person = {
    name : 'henry',

    callMe : function() {
        alert(this.name);
    }
};
callbackTest(person.callMe, person);


Using callback.call, we can specify what scope the callback function should refer to. We can use callback.apply as well. call and apply perform exactly the same, except one difference:
call accepts the object to resolve scope to, and then parameters, like callback.call(object, val1, val2); apply accepts the object to resolve to, and then an array of parameters, like callback.apply(object,[val1,val2,val3]);

No comments: