I need to understand the difference between Variable Environment vs lexical environment in JavaScript. Actually I go through some notes available in stackoverflow and the doc bu
This answer relates to ECMA-262 ed 5.1. Later editions have modified the description of variable and lexical environments to accommodate lexical scoping of let and const (which are both block scoped).
According to ECMA-262 §10.3, variable environment is a certain type of lexical environment. Both are "specification types" that are used solely to describe features of ECMAScript. You can't access them or modify them directly in anyway, and ECMAScript implementations don't have to implement them in any specific way, they just have to behave as if they were implemented per the specification.
A lexical environment consists of an environment record, which can be thought of as an object whose properties are the variable and function names declared within the associated execution context. It also has, for functions, identifiers from the formal parameter list in the function declaration or expression (e.g. function foo(a, b){}
effectively declares a
and b
as variables on foo's environment record).
A lexical environment also has a link to any outer lexical environment (i.e. its scope chain), so it is used to resolve identifiers outside the current an execution context (e.g. global variables from within a function). They can be associated with other structures besides functions and execution contexts, e.g. try..catch and with statements.
A variable environment is just the part of a lexical environment within the execution context, essentially just the variables and functions declared within the current context.
Anyone who wants to correct the above, please chime in.
It's easier to understand the Lexical Environment here when you break it up into two more underlying concepts, the Variable Environment and the Outer Environment.
Each Execution Context has an Outer Environment and a Variable Environment. The Outer Environment and the Variable Environment make up the Lexical Environment. That is, the Variable Environment is a certain type of Lexical Environment. A Lexical Environment can be thought of as an internal JavaScript engine construct that holds identifier-variable mappings. The identifier is the name of the variable or function and variable is a reference to the actual data type stored by the identifier (e.g. Object, Number, String, Boolean, null, undefined). A Lexical Environment also has a link to any Outer Environment (i.e. its scope chain), so it is used to resolve identifiers outside the current Execution Context. Ultimately, a corresponding Lexical Environment is created for every Execution Context.
In simplest terms, Variable Environment refers to where the variables live that you created. In the below example, each myVar is distinct and they do not touch each other. First, a global Execution Context is created. In the Creation Phase of the Execution Context, both the Outer Environment and Variable Environment of the Lexical Environment is created. In terms of the Variable Environment, myVar is placed in memory with undefined value and b and a functions are put into memory with a reference to their definitions. These properties are attached to the global object referenced by 'this'. The properties we defined in the global Execution Context are stored in the global Variable Environment. Then in the Execution Phase of the Execution Context, myVar is assigned the value of 1. So now in memory for the global Execution Context, myVar has a value of 1. This is stored in the global Variable Environment.
Then during execution, the a function is invoked. A new Execution Context is created and put on the Execution Stack. It goes through the Creation and Execution Phase of the Execution Context. The myVar variable declared here is placed in a new area in memory separate from the other Execution Context. A Lexical Environment is created mapping the identifier to its variable. It in effect creates a Variable Environment for any variables defined in this Execution Context. This Variable Environment is distinct from any other Variable Environment. The Execution Phase occurs and myVar is assigned a value of 2. Now in this Variable Enviroment, myVar has a value of 2, whereas in the global ExecutionContext, myVar has a value of 1. Note that if we were to refer to a variable in this new Execution Context that does not exist in the current Execution Context, then the Lexical Environment will search its parent Lexical Environment for the variable, aka the Outer Environment. Since JavaScript is single-threaded, once myVar is assigned a value of 2, it moves on to the next statement, which is the invocation of b, and a new Execution Context is created for b and the same process occurs again.
function b(){
var myVar;
console.log(myVar);
}
function a(){
var myVar = 2;
console.log(myVar);
b();
}
var myVar = 1;
console.log(myVar);
a();
console.log(myVar);
> 1
> 2
> undefined
> 1
Again, it's important to emphasize that each of the myVars live in their own Variable Enviroment respective to their own Execution Context. Hence, when we console.log(myVar) after calling the a function, myVar in the global Execution Context is still the value of 1. In fact, when we do the second console.log(myVar), both the a and b function's Execution Context will already be popped.
It's also very important to note here that since these functions are invoked without the new keyword, this refers to the object in the global Execution Context. This can be proven easily:
var a = 1;
function b(){
var a = 2;
console.log(this.a);
}
b()
> 1
Above, this.a refers to the a defined in the global Execution Context, since this is refering to the global object, which in browsers is the Window object.
Now that we discussed the Variable Environment, let's discuss the Outer Enviroment of the Lexical Environment. This leads us to the Scope Chain. First, we must ask what is an Outer Environment of an Execution Context? In the below example, in the case of the function b, its Outer Environment is the Global Execution Context. This is also the case of the function a. This is true for function b, even though function a is directly below function b in the Execution Stack. The Outer Environment invokes the concept of the Lexical Environment. The Lexical Environment highlights the idea that where something is written physically in your code is important. It determines how identifier/variable mappings live in your code and how they will connect to each other. So we must ask ourselves, where does function b sit lexically? It lexically sits on top of the global Outer Environment. The function b is not sitting inside of function a; instead, it is sitting at the same level as the global Outer Environment. When you ask for a variable while running a line of code within any particular Execution Context, if the Engine cannot find the variable within the Variable Environment of the current Execution Context, it will look for the variables in the Outer Environment of the current Execution Context. Now even if there are 10 Execution Contexts' stacked on each other, if the 10th Execution Context is sitting lexically on the global Outer Environment, when it searches for its Outer Environment in each of the other 9 Execution Contexts', it will search all the way to the global Execution Context, since the code sits lexically on the Outer Environment. This process of searching for a given Execution Context's Outer Environment is known as the Scope Chain in JavaScript. Remember scope asks 'where can I access a variable?'. And the Scope Chain is those links of Outer Environment references.
function b(){
console.log(myVar);
}
function a(){
var myVar = 2;
b();
}
var myVar = 1;
a();
> 1
You might have thought that myVar in b would be 2, since the b function would look into its parent Execution Context, which is a (which is the Execution Context directly below b's Execution Context in the Execution Stack). But that is not how the Outer Environment of the Lexical Environment works. Because the Outer Environment of b is the global Execution Context, myVar in b will be the value 1.
Now it's possible to change the Lexical Environment of a function. We can change the lexical environment of the function b by placing it physically inside of function a. Since we changed its physical location, the Outer Environment of the Lexical Environment of the function b changes.
LexicalEnvironment
and VariableEnvironment
are what keep track of variables during runtime and correspond to block scope and function/module/global scope respectively. Here's an example based on my reading of the spec http://www.ecma-international.org/ecma-262/6.0/
0: function do_something() {
1: var a = 1;
2: let b = 2;
3: while (true) {
4: var c = 3;
5: let d = 4;
6: console.log(b);
7: break;
8: }
9: }
10:
11: do_something();
When we first call do_something()
it creates an ExecutionContext
.
ExecutionContext:
LexicalEnvironment:
b -> nothing
outer: VariableEnvironment //here should VariableEnvironment
VariableEnvironment:
a -> undefined, c -> undefined
outer: global
...
Entering the while
loop creates a new lexical environment:
ExecutionContext:
LexicalEnvironment:
d -> nothing
outer:
LexicalEnvironment
b -> 2
outer: global
VariableEnvironment:
a -> 1, c -> undefined
outer: global
...
Now when we look up variables, we can always fall back on whatever is contained in outer
. This is why you can access global variables from within functions. It is also why we can access console.log(b)
from within the while
block even though it lives in the outer scope.
When we leave the while block we restore the original lexical environment.
ExecutionContext:
LexicalEnvironment
b -> 2
outer: global
VariableEnvironment:
a -> 1, c -> 3
outer: global
Therefore d
is no longer accessible.
Then when we leave the function we destroy the execution context.
I think that's the gist of it.
Although this explanation is based on ECMA-262 6.0 because of let
, the LexicalEnvironment is defined similarly in 5.1 where it's used to temporarily bind variables in with
, and in the catch
clause of a try/catch
.