Search

Rss Posts

Rss Comments

Login

 

Scope/Context in Javascript

February 8, 2009

Scope or context as it is often referred to as is one of the fundamental aspects of Javascript, playing a significant role unlike that of any other language. Scope refers to the accessibility of variables and its execution context which belongs to the object or function in which it was defined. However this does not always hold true as scope can be lost or changed depending on how and where it used, specifically as it applies to functional execution.

Each invocation of a function establishes a unique execution context that lasts only for the duration of that function call. Each execution context appends its local scope to the scope chain in which it was defined. Name conflicts amongst variables within a specific context are resolved by climbing up the scope chain, moving locally to globally. This means that local variables with the same name as variables higher up the scope chain take precedence.

Global/Local Scope

A variable can be defined in either local or global scope, which establishes the variables’ accessibility and context within the runtime environment. Any defined global variables, meaning any variables declared outside of a function body, live throughout the life of the page and can be accessed and altered in any context.

Local variables exist only within a function body of which they are defined. A local variable has a different execution context for every invocation of the function to which it belongs; it is subject for value assignment, retrieval, and manipulation only within that call and is non-accessible outside of that execution context.

Losing Scope

It’s a common problem developers find themselves dealing with when they delve into advanced techniques in Javascript, specifically as it refers to event handling and object orientation. Here the infamous this keyword plays an important role, but knowing what it is referring to at all times can be confusing.

Event handlers added using Internet Explorer’s attachEvent method causes a loss of scope when that event is fired and the callback is invoked, as the function is executed in the context of the window object. As a result, this will reference the window and not the element which registered the event handler as is the case with standards compliant browsers such as Firefox. Alternatively, when an event handler is added as an expando property (element.onclick), the callback will execute in the context of the element, even in IE. The reason for this is the scope chain, because the expando is a  property of the source element, it is appended at the top of the scope chain for each execution context. Meanwhile, due to a flaw in attachEvent, the window object is the only object associated with the callback and therefore is the only context available in the callback’s scope chain.

To better demonstrate this, try adding a function as a unique expando property and then execute it; this will now refer back to the source element:

var fn = function(){
    alert(this.id);
};
 
element['e'+fn] = fn;
element['e'+fn]();

Scope is also lost when dealing with object-orientation and event handling, which is not so much a flaw in the language but the implementation. Event handlers can only be added to DOM nodes which means (in standards-compliant browsers) this will always refer back to the source element, which is the way it should be. However if the callback happens to be an instance method, there is no way to properly call that method without altering scope. A quick solution to this is to declare a variable with the value of this and simply use a nested function to redirect the call to the desired method, like so:

var self = this;
element.onclick = function(){
    self.instanceMethod()
}

However, this can be cumbersome, the real solution involves what many believe to be one of the most useful techniques of the Javascript langauge; manual scope adjustment (more on than a little further down in the article).

Closures

One of the most powerful aspects to Javascript is the flexibility of functions; they can be sent as arguments, nested inside other functions, executed in any context desired, and encapsulate their own execution context. Closures can serve as quite the tool when scope becomes an issue as they can take on many different forms and can encapsulate and preserve a specific execution context for further manipulation.  In simplest terms, closures are variable functions local to another function that have the unique ability to retain their values between function calls. The most popular closure is what is widely known as the module pattern; it allows you to simulate public, private, and privileged members.

Module = function(){
    var privateProp = "";
    var privateMethod = function(args){
        //do something
    }
 
    return {
 
        publicProp: "",
 
        publicMethod: function(args){
            //do something
        },
 
        privilegedMethod: function(args){
            privateFunction(args);
        }
    }
}()

The module acts as if it were a singleton executed as soon as the compiler interprets it (hence the opening and closing parenthesis at the end of the function). The only available members outside of the execution context of the closure are your public methods and properties located in the return object (ex. Module.publicMethod). However, all private properties and methods will live throughout the life of the application as the execution context is preserved, meaning variables are subject to further manipulation and execution via the public methods. Another type of closure is what I like to call a global closure which is nothing more than a self-invoked anonymous function executed in the context of the window:

(function(){
 
    //Use this or window to define a global variable
    var Module = this.Module = window.Module = function(){
        //do something
    }
 
    Module.publicProp = "";
    Module.publicMethod = function(){
        //do something
    }
 
    var privateProp = "";
    var privateMethod = function(){
        //do something
    }
 
})()

The global closure is most useful when attempting to preserve the global namespace as any variables declared within the function body will be local to the closure but will still live throughout runtime, often one namespace is enough for interaction outside of the closure.

Call and Apply

If you want to know how all the mainstream libraries do it, well it’s simple; call and apply. These two very simple methods inherent to all functions allow you to execute any function in any desired context. Before I continue, it is important to know that everything in Javascript inherently has its own context, including DOM nodes, objects, arrays, strings, functions, etc. Meaning, any function can be executed in the context of any object using call and apply. This may seem insignificant, but understand it is this implementation that serves as an underlying bridge between the object-oriented and functional methodologies in the Javascript language.

The call method contains an unspecified amount of arguments, the first is the object whose context the function is to be executed in, all subsequent arguments will serve as the arguments for the callback function in question. Apply works similarly with one exception, rather than supplying each argument individually, an array will be supplied that will automatically build the arguments for the function.

method.call(scope, arg1, arg2, arg3);
method.apply(scope, [args]);

This is ultimately what should be used when referring to event handlers especially as it relates to object-orientation. One of the more popular functions used to quickly adjust context is the bind function, which utilizes closures by accepting the scope as well as arguments for the callback function and returning an anonymous function that behaves appropriately.

Function.prototype.bind = function(){
    var method = this;
    var context = arguments[0];
    var args = Array.prototype.slice.call(arguments, 1)
    return function(){
        method.apply(context, args);
    }
}

It is most commonly used where context is most commonly lost; event handling as well as a few other implementations such as  setInterval and setTimeout. The following example demonstrates a callback method bound to a specific context for execution for each loop of an interval:

setInterval(callback.bind(obj));

What is important to note here is that setInterval is not executing the bind function or the callback function. In fact it is the closure that was returned by the bind function that is being called on each loop of the interval. It promptly redirects the call to the callback function in the context that was supplied to the bind function: “obj”. While reviewing the source of the bind function, you may have also noticed what appears to be a relatively simple line of code involving a method of the Array object’s prototype:

var args = Array.prototype.slice.call(arguments, 1);

What is interesting to note here is that the arguments object is not actually an array at all, however it is often described as an array-like object much like a nodelist (anything returned by getElementsByTagName or element.childNodes). They contain a length property and indexed values but they are still not arrays, and subsequently don’t support any of the native methods of array such as slice and push. However, because of their similar behaviour, the methods of Array can be hijacked and executed in the context of an array-like object as is the case above.

This technique also applies to the methods of classes; making it easy to execute a method of a prototype chain in the context of a another instance and have all properties and methods of that instance immediately recognized and subject to execution and manipulation. The following example demonstrates a method of the Module class calling a method of the Component class in the context of the Module instance. This is also what makes calls to a superclass possible when simulating inheritance in Javascript.

Module.prototype.method = function(){
    //call another method of another class in the context of this instance
    Component.prototype.method.call(this, arg1, arg2, arg3);
}

Summary

Versatility is the name of the game here, in the attempt to try and do everything Javascript fails to present developers with a well integrated language, resulting in a very loose implementation. However, there is a method to the madness, and although these little quirks and workarounds are not ideal they are necessary if you want that efficient and effective app that we all strive for. All the techniques I’ve discussed here today are powerful ones, and they can work against you if implemented in the wrong fashion or the wrong place. Closures for example can reserve a lot of memory and ultimately cause memory leaks in IE. However, when done right, developing will never be easier and it will show, knowing your scope at all times is important and to know it is to utilize it, which will save you a world of headaches now and in the future.

References

Post a comment