Scope and Closures

In JavaScript, scope is the context in which code is executed. There are three types of scope: global scope, local scope (sometimes referred to as "function scope"), and eval scope.

Code defined using var inside of a function is locally scoped, and is only "visible" to other expressions in that function, which includes code inside any nested/child functions. Variables defined in the global scope can be accessed from anywhere because it is the highest level and last stop in the scope chain.

Examine the code that follows and make sure you understand that each declaration of foo is unique because of scope.

Sample: sample110.html

Make sure you understand that each foo variable contains a different value because each one is defined in a specifically delineated scope.

An unlimited number of function and eval scopes can be created, while only one global scope is used by a JavaScript environment.

The global scope is the last stop in the scope chain.

Functions that contain functions create stacked execution scopes. These stacks, which are chained together, are often referred to as the scope chain.


JavaScript Does Not Have Block Scope

Since logic statements (if) and looping statements (for) do not create a scope, variables can overwrite each other. Examine the following code and make sure you understand that the value of foo is being redefined as the program executes the code.

Sample: sample111.html

So foo is changing as the code executes because JavaScript has no block scope—only function, global, or eval scope.


Use var Inside of Functions to Declare Variables and Avoid Scope Gotchas

JavaScript will declare any variables lacking a var declaration (even those contained in a function or encapsulated functions) to be in the global scope instead of the intended local scope. Have a look at the code that follows and notice that without the use of var to declare bar, the variable is actually defined in the global scope and not the local scope, where it should be.

Sample: sample112.html

The concept to take away here is that you should always use var when defining variables inside of a function. This will prevent you from dealing with potentially confusing scope problems. The exception to this convention, of course, is when you want to create or change properties in the global scope from within a function.


The Scope Chain (aka Lexical Scoping)

There is a lookup chain that is followed when JavaScript looks for the value associated with a variable. This chain is based on the hierarchy of scope. In the code that follows, I am logging the value of sayHiText from the func2 function scope.

Sample: sample113.html

How is the value of sayHiText found when it is not contained inside of the scope of the func2 function? JavaScript first looks in the func2 function for a variable named sayHiText. Not finding func2 there, it looks up to func2s parent function, func1. The sayHiText variable is not found in the func1 scope, either, so JavaScript then continues up to the global scope where sayHiText is found, at which point the value of sayHiText is delivered. If sayHiText had not been defined in the global scope, undefined would have been returned by JavaScript.

This is a very important concept to understand. Let's examine another code example, one in which we grab three values from three different scopes.

Sample: sample114.html

The value for z is local to the bar function and the context in which the console.log is invoked. The value for y is in the foo function, which is the parent of bar(), and the value for x is in the global scope. All of these are accessible to the bar function via the scope chain. Make sure you understand that referencing variables in the bar function will check all the way up the scope chain for the variables referenced.

The scope chain, if you think about it, is not that different from the prototype chain. Both are simply a way for a value to be looked up by checking a systematic and hierarchical set of locations.


The Scope Chain Lookup Returns the First Found Value

In the code sample that follows, a variable called x exists in the same scope in which it is examined with console.log. This "local" value of x is used, and one might say that it shadows, or masks, the identically named x variables found further up in the scope chain.

Sample: sample115.html

Remember that the scope lookup ends when the variable is found in the nearest available link of the chain, even if the same variable name is used further up the chain.


Scope Is Determined During Function Definition, Not Invocation

Since functions determine scope and functions can be passed around just like any JavaScript value, one might think that deciphering the scope chain is complicated. It is actually very simple. The scope chain is decided based on the location of a function during definition, not during invocation. This is also called lexical scoping. Think long and hard about this, as most people stumble over it often in JavaScript code.

The scope chain is created before you invoke a function. Because of this, we can create closures. For example, we can have a function return a nested function to the global scope, yet our function can still access, via the scope chain, its parent function's scope. In the following sample, we define a parentFunction that returns an anonymous function, and we call the returned function from the global scope. Because our anonymous function was defined as being contained inside of parentFunction, it still has access to parentFunctions scope when it is invoked. This is called a closure.

Sample: sample116.html

The idea you should take away here is that the scope chain is determined during definition literally in the way the code is written. Passing around functions inside of your code will not change the scope chain.


Closures Are Caused by the Scope Chain

Take what you have learned about the scope chain and scope lookup from this article, and a closure should not be overly complicated to understand. In the following sample, we create a function called countUpFromZero. This function actually returns a reference to the child function contained within it. When this child function (nested function) is invoked, it still has access to the parent function's scope because of the scope chain.

Sample: sample117.html

Each time the countUpFromZero function is invoked, the anonymous function contained in (and returned from) the countUpFromZero function still has access to the parent function's scope. This technique, facilitated via the scope chain, is an example of a closure.


Conclusion

If you feel I have over-simplified closures, you are likely correct in this thought. But I did so purposely as I believe the important parts come from a solid understanding of functions and scope, not necessarily the complexities of execution context. If you are in need of an in-depth dive into closures, have a look at JavaScript Closures.

Tags:

Comments

Related Articles