Identifier Resolution and Closures in the JavaScript Scope Chain

From my previous post, we now know that every function has an associated execution context that contains a variable object [VO], which is composed of all the variables, functions and parameters defined inside that given local function.

The scope chain property of each execution context is simply a collection of the current context's [VO] + all parentexecution context's [VO].

Scope = VO + All Parent VOs
Eg: scopeChain = [ [VO] + [VO1] + [VO2] + [VO n+1] ];

Determining a Scope Chain’s Variable Objects [VO]s

We now know that the first [VO] of the scope chain belongs to the current execution context, and we can find the remaining parent [VO]s by looking at the parent context’s scope chain:

function one() {

    two();

    function two() {

        three();

        function three() {
            alert('I am at function three');
        }

    }

}

one();

The example is straight forward, starting from the global context we call one()one()calls two(), which in turn calls three(), thus alerting that it is at function three. The image above shows the call stack at function three at the time alert('I am at function three') is fired. We can see that the scope chain at this point in time looks as follows:

three() Scope Chain = [ [three() VO] + [two() VO] + [one() VO] + [Global VO] ];

Lexical Scope

An important feature of JavaScript to note, is that the interpreter uses Lexical Scoping, as opposed to Dynamic Scoping. This is just a complicated way of saying all inner functions, are statically (lexically) bound to the parent context in which the inner function was physically defined in the program code.

In our previous example above, it does not matter in which sequence the inner functions are called. three() will always be statically bound to two(), which in turn will always be bound toone() and so on and so forth. This gives a chaining effect where all inner functions can access the outer functions VO through the statically bound Scope Chain.

This lexical scope is the source of confusion for many developers. We know that every invocation of a function will create a newexecution context and associated VO, which holds the values of variables evaluated in the current context.

It is this dynamic, runtime evaluation of the VO paired with the lexical (static) defined scope of each context that leads unexpected results in program behaviour. Take the following classic example:

var myAlerts = [];

for (var i = 0; i < 5; i++) {
    myAlerts.push(
        function inner() {
            alert(i);
        }
    );
}

myAlerts[0](); // 5
myAlerts[1](); // 5
myAlerts[2](); // 5
myAlerts[3](); // 5
myAlerts[4](); // 5

At first glance, those new to JavaScript would assume alert(i); to be the value of i on each increment where the function was physically defined in the source code, alerting 1, 2, 3, 4 and 5 respectively.

This is the most common point of confusion. Function inner was created in the global context, therefore it’s scope chain is statically bound to the global context.

Lines 11 ~ 15 invoke inner(), which looks in inner.ScopeChain to resolve i, which is located in the global context. At the time of each invocation, i, has already been incremented to 5, giving the same result every time inner() is called. The statically bound scope chain, which holds [VOs] from each context containing live variables, often catches developers by surprise.

Resolving the value of variables

The following example alerts the value of variables ab and c, which gives us a result of 6.

function one() {

    var a = 1;
    two();

    function two() {

        var b = 2;
        three();

        function three() {

            var c = 3;
            alert(a + b + c); // 6

        }

    }

}

one();

Line 14 is intriguing, at first glance it seems that a and b are not “inside” function three, so how can this code still work? To understand how the interpreter evaluates this code, we need to look at the scope chain of function three at the time line 14 was executed:

When the interpreter executes line 14: alert(a + b + c), it resolvesa first by looking into the scope chain and checking the first variable object, three's [VO]. It checks to see if a exists insidethree's [VO] but can not find any property with that name, so moves on to check the next [VO].

The interpreter keeps checking each [VO] in sequence for the existence of the variable name, in which case the value will be returned to the original evaluated code, or the program will throw a ReferenceError if none is found. Therefore, given the example above, you can see that ab and c are all resolvable given function three’s scope chain.

How does this work with closures?

In JavaScript, closures are often regarded as some sort of magical unicorn that only advanced developers can really understand, but truth be told it is just a simple understanding of the scope chain. A closure, as Crockford says, is simply:

An inner function always has access to the vars and parameters of its outer function, even after the outer function has returned…

The code below is an example of a closure:

function foo() {
    var a = 'private variable';
    return function bar() {
        alert(a);
    }
}

var callAlert = foo();

callAlert(); // private variable

The global context has a function named foo() and a variable named callAlert, which holds the returned value of foo(). What often surprises and confuses developers is that the private variable,a, is still available even after foo() has finished executing.

However, if we look at each of the context in detail, we will see the following:

// Global Context when evaluated
global.VO = {
    foo: pointer to foo(),
    callAlert: returned value of global.VO.foo
    scopeChain: [global.VO]
}

// Foo Context when evaluated
foo.VO = {
    bar: pointer to bar(),
    a: 'private variable',
    scopeChain: [foo.VO, global.VO]
}

// Bar Context when evaluated
bar.VO = {
    scopeChain: [bar.VO, foo.VO, global.VO]
}

Now we can see by invoking callAlert(), we get the functionfoo(), which returns the pointer to bar(). On entering bar(),bar.VO.scopeChain is [bar.VO, foo.VO, global.VO].

By alerting a, the interpreter checks the first VO in thebar.VO.scopeChain for a property named a but can not find a match, so promptly moves on to the next VO, foo.VO.

It checks for the existence of the property and this time finds a match, returning the value back to the bar context, which explains why the alert gives us 'private variable' even thoughfoo() had finished executing sometime ago.

By this point in the article, we have covered the details of thescope chain and it’s lexical environment, along with howclosures and variable resolution work. The rest of this article looks at some interesting situations in relation to those covered above.

Wait, how does the prototype chain affect variable resolution?

JavaScript is prototypal by nature and almost everything in the language, except for null and undefined, are objects. When trying to access a property on an object, the interpreter will try to resolve it by looking for the existence of the property in the object. If it can’t find the property, it will continue to look up theprototype chain, which is an inherited chain of objects, until it finds the property, or traversed to the end of the chain.

This leads to an interesting question, does the interpreter resolve an object property using the scope chain or prototype chain first ? It uses both. When trying to resolve a property or identifier, thescope chain will be used first to locate the object. Once theobject has been found, the prototype chain of that object will then be traversed looking for the property name. Let’s look at an example:

var bar = {};

function foo() {

    bar.a = 'Set from foo()';

    return function inner() {
        alert(bar.a);
    }

}

foo()(); // 'Set from foo()'

Line 5 creates the property a on the global object bar, and sets its value to 'Set from foo()'. The interpreter looks into thescope chain and as expected finds bar.a in the global context. Now, lets consider the following:

var bar = {};

function foo() {

    Object.prototype.a = 'Set from prototype';

    return function inner() {
        alert(bar.a);
    }

}

foo()(); // 'Set from prototype()'

At runtime, we invoke inner(), which tries to resolve bar.a by looking in it’s scope chain for the existence of bar. It finds bar in the global context, and proceeds to search bar for a property named a. However, a was never set on bar, so the interpreter traverses the object’s prototype chain and finds a was set onObject.prototype.

It is this exact behavior which explains identifier resolution; locate the object in the scope chain, then proceed up the object’sprototype chain until the property is found, or returned undefined.

When to use Closures?

Closures are a powerful concept given to JavaScript and some of the most common situations to use them are:

  • Encapsulation

    Allows us to hide the implementation details of a context from outside scopes, while exposing a controlled public interface. This is commonly referred to as the module pattern or revealing module pattern.

  • Callbacks

    Perhaps one of the most powerful uses for closures are callbacks. JavaScript, in the browser, typically runs in a single threaded event loop, blocking other events from starting until one event has finished. Callbacks allow us to defer the invocation of a function, typically in response to an event completing, in a non-blocking manner. An example of this is when making an AJAX call to the server, using a callback to handle to response, while still maintaining the bindings in which it was created.

  • Closures as arguments

    We can also pass closures as arguments to a function, which is a powerful functional paradigm for creating more graceful solutions for complex code. Take for example a minimum sort function. By passing closures as parameters, we could define the implementation for different types of data sorting, while still reusing a single function body as a schematic.

When not to use Closures ?

Although closures are powerful, they should be used sparingly due to some performance concerns:

  • Large scope lengths

    Multiple nested functions are a typical sign that you might run into some performance issues. Remember, every time you need to evaluate a variable, the Scope Chain must be traversed to find the identifier, so it goes without saying that the further down the chain the variable is defined, the longer to lookup time.

Garbage collection

JavaScript is a garbage collected language, which means developers generally don’t have to worry about memory management, unlike lower level programming languages. However, this automatic garbage collection often leads developers application to suffer from poor performance and memory leaks.

Different JavaScript engines implement garbage collection slightly different, since ECMAScript does not define how the implementation should be handled, but the same philosophy can apply across engines when trying to create high performance, leak free JavaScript code. Generally speaking, the garbage collector will try to free the memory of objects when they can not be referenced by any other live object running in the program, or are unreachable.

Circular references

This leads us to closures, and the possibility of circular references in a program, which is a term used to describe a situation where one object references another object, and that object points back to the first object. Closures are especially susceptible to leaks, remember that an inner function can reference a variable defined further up the scope chain even after the parent has finished executing and returned. Most JavaScript engines handle these situations quite well (damn you IE), but it’s still worth noting and taking into consideration when doing your development.

For older versions of IE, referencing a DOM element would often cause you memory leaks. Why? In IE, the JavaScript (JScript ?) engine and DOM both have their own individual garbage collector. So when referencing a DOM element from JavaScript, the native collector hands off to the DOM and the DOM collector points back to native, resulting in neither collector knowing about the circular reference.

Summary

From working with many developers over the past few years, I often found that the concepts of scope chain and closures were known about, but not truly understood in detail. I hope this article has helped to take you from knowing the basic concept, to an understanding in more detail and depth.

Going forward, you should be armed with all the knowledge you need to determine how the resolution of variables, in any situation, works when writing your JavaScript. Happy coding !

frameborder="0" hspace="0" marginheight="0" marginwidth="0" scrolling="no" tabindex="0" vspace="0" width="100%" id="I0_1431613681179" name="I0_1431613681179" src="https://apis.google.com/u/0/se/0/_/+1/fastbutton?usegapi=1&size=tall&annotation=bubble&hl=en-US&origin=http%3A%2F%2Fdavidshariff.com&url=http%3A%2F%2Fdavidshariff.com%2Fblog%2Fjavascript-scope-chain-and-closures%2F&gsrc=3p&ic=1&jsh=m%3B%2F_%2Fscs%2Fapps-static%2F_%2Fjs%2Fk%3Doz.gapi.zh_CN.2mYzUHA7TBo.O%2Fm%3D__features__%2Fam%3DAQ%2Frt%3Dj%2Fd%3D1%2Ft%3Dzcms%2Frs%3DAGLTcCME5ABcH1uVpubtB5yDomMFi1OhUA#_methods=onPlusOne%2C_ready%2C_close%2C_open%2C_resizeMe%2C_renderstart%2Concircled%2Cdrefresh%2Cerefresh%2Conload&id=I0_1431613681179&parent=http%3A%2F%2Fdavidshariff.com&pfname=&rpctoken=10274319" data-gapiattached="true" title="+1" style="display: block; margin: 0px; padding: 0px; max-width: 657px; float: none; min-height: 250px; min-width: 0px; position: static; top: 0px; width: 50px; border-style: none; left: 0px; visibility: visible; height: 60px;">
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值