I've talked about variable scopes in Variables. One focus of the article is that variables declared with var have the same scope of the enclosing environment, but variables declared without var are properties of a global object. For JavaScript, this is enough to explain difference between using and not using var.
In Closures, the concept of free variables is enough to explain what a closure is.
In fact, when JavaScript looks up a variable, it'll follow a scope chain to determine whether a variable exists or not. The scope chain mechanism explains why JavaScript has no block scope and how it implements a closure.
To understand scope chains, we should understand the lexical scope of a function. The lexical scope is defined by the physical placement of a function within the code. For example:
var x = 10;Physically, the function inner is wrapped by outer and outer is wrapped by the global context. This physical structure is statically defined and changeless in the code.
function outer() {
var y = 20;
function inner() {
var z = 30;
}
}
func();
The dynamic counterpart to the static lexical scope is the execution context created by every invocation of a function. Every piece of JavaScript code is executed in the execution context. Take the above code for example; a variable x is defined in the global execution context. Each invocation of a function, however, will create a new function execution context and then an activation object, also called a call object, with an argument property.
Every function has an internal [[scope]] property (cannot be accessed directly). Every time the execution environment encounters a function, it will specify [[scope]] a scope chain and the call object of the function will be the first element. And then, variable declarations in the function are processed. Every variable of a function will be a property of the call object. (The call object is also called a variable object right now.)
You cannot access [[scope]] of a function directly but Rhino interpreter gives you a non-standard __parent__ property to retrieve the call object of the enclosing function. Take the above code for example; outer.__parent__ is the call object of the global environment which wraps the outer function. For a top level function, that is the global object, the only object in the scope chain at that time. You can get the call object of outer through inner.__parent__. If you want to get next object in the scope chain, you can use inner.__parent__.__parent__. What you get is the global object this time.
This process seems a little complex. They are nuts and bolts of an interpreter. In conclusion, you can look up variables along a scope chain. This is the basic way to search a variable in JavaScript. For example:
js> function outer() {
> var y = 20;
> function inner() {
> var z = 30;
> }
> return inner;
> }
js> var f = outer();
js> outer.__parent__ == this;
true
js> f.__parent__.__parent__ == this;
true
js> f.__parent__.y;
20
js> f.__parent__.__parent__.x;
10
js>
> var y = 20;
> function inner() {
> var z = 30;
> }
> return inner;
> }
js> var f = outer();
js> outer.__parent__ == this;
true
js> f.__parent__.__parent__ == this;
true
js> f.__parent__.y;
20
js> f.__parent__.__parent__.x;
10
js>
A variable is actually a property of a call object. (Activation Object and Variable Object are synonyms of Call Object.) For the above code, you can refer to the call object of inner by inner.__parent__. The variable y defined in outer is actually a property of the call object. Using f.__parent__.__parent__ will get next object in the scope chain. That's the global object.
Take the following for example:
js> function func() {
> print(m);
> var m = 10;
> print(m);
> }
js> func();
undefined
10
js>
> print(m);
> var m = 10;
> print(m);
> }
js> func();
undefined
10
js>
If you look at it step by step, it's just like:
function func() {Using the non-standard __parent__, you can also get the same results:
print(m); // variableObject = { m : undefined };
var m = 10; // variableObject = { m : 10 };
print(m); // variableObject = { m : 10 };
}
js> function func() {
> function inner() {}
> print(inner.__parent__.m);
> var m = 10;
> print(inner.__parent__.m);
> }
js> func();
undefined
10
js>
> function inner() {}
> print(inner.__parent__.m);
> var m = 10;
> print(inner.__parent__.m);
> }
js> func();
undefined
10
js>
While JavaScript is searching a variable, it will search the first object in the scope chain. If there's no corresponding property on it, try to search next object of the scope chain. If it's still not found, search the third object and so on. Continue this process until the global object is reached.
Let's see why a local variable overwrites a global variable.
var x = 10;We can explain it from points of a scope chain. While searching a variable x, it will see if there's a property x on the call object of func and the value is 20.
function func() {
var x = 20;
print(x);
}
js> var x = 10;
js> function func() {
> function inner() {}
> var x = 20;
> print(inner.__parent__.x);
> }
js> func();
20
js>
js> function func() {
> function inner() {}
> var x = 20;
> print(inner.__parent__.x);
> }
js> func();
20
js>
Let's see a closure example:
function doSome() {The call object of f has no property x so trying to search x on the call object of the enclosing doSome. That is to search an x property on f.__parent__ and find it this time.
var x = 10;
function f(y) {
return x + y;
}
return f;
}
If you understand the concept of a scope chain, combine with the non-standard __parent__, you can play magic as follows:
js> function func() {
> function inner() {}
> inner.__parent__.y = 30;
> print(y);
> }
js> func();
30
js>
> function inner() {}
> inner.__parent__.y = 30;
> print(y);
> }
js> func();
30
js>
Even though func doesn't use var to declare y, you can still print the value of y.
So you can say that, in JavaScript, all variables are properties of an object.
By the way, if you use new and the Function constructor to create a function, it always searches properties of the global object to find a variable. For example:
js> var x = 10;
js> function func() {
> var x = 20;
> var f = new Function('return x;');
> print(f.__parent__.x);
> return f();
> };
js> func();
10
10
js>
js> function func() {
> var x = 20;
> var f = new Function('return x;');
> print(f.__parent__.x);
> return f();
> };
js> func();
10
10
js>