Mechanics of variables and functions in JavaScript is completely different from most other languages.
Advanced topics become easy to grasp once you know how it works.
In JavaScript, all local variables and functions are properties of the special internal object, called LexicalEnvironment
.
The top-level LexicalEnvironment
in browser is window
. It is also called a global object.
Instantiation of top-level variables
When the script is going to be executed, there is a pre-processing stage called variables instantiation.
- First, the interpreter scans the code for
<a href="/tutorial/functions-declarations-and-expressions">Function Declarations</a>
, which are declared asfunction name {...}
in the main code flow.It takes every declaration, creates the function from it and puts it into
window
.For example, consider the code:
var a = 5 function f(arg) { alert('f:'+arg) } var g = function(arg) { alert('g:'+arg) }
At this stage, the browser finds
function f
, creates the function and stores it aswindow.f
:// 1. Function Declarations are initialized before the code is executed. // so, prior to first line we have: window = { f: function } var a = 5 *!*function f(arg)*/!* { alert('f:'+arg) } // <-- FunctionDeclaration var g = function(arg) { alert('g:'+arg) }
As a side effect,
f
can be called before it is declared:f() function f() { alert('ok') }
- Second, the interpreter scans for
var
declarations and createswindow
properties. Assignments are not executed at this stage. All variables start asundefined
.// 1. Function declarations are initialized before the code is executed. // window = { f: function } // 2. Variables are added as window properties. // window = { f: function, a: undefined, g: undefined } *!*var a*/!* = 5 // <-- var function f(arg) { alert('f:'+arg) } *!*var g*/!* = function(arg) { alert('g:'+arg) } // <-- var
The value of
g
is a function expression, but the interpreter doesn’t care. It creates variables, but doesn’t assign them.So to sum:
FunctionDeclarations
become ready-to-use functions. That allows to call a function before it’s declaration.- Variables start as
undefined
. - All assignments happen later, when the execution reaches them.
As a side effect, it is impossible to have a variable and a function with the same name.
- Then the code starts running.
When a variable or function is accessed, the interpreter gets it from
window
:alert("a" in window) // true, because window.a exists alert(a) // undefined, because assignment happens below alert(f) // function, because it is Function Declaration alert(g) // undefined, because assignment happens below var a = 5 function f() { /*...*/ } var g = function() { /*...*/ }
-
After the assignments,
a
becomes5
andg
becomes a function. In the code below,alerts
are moved below. Note the difference:var a = 5 var g = function() { /*...*/ } alert(a) // 5 alert(g) // function
If a variable is not declared with
var
, then, of course, it doesn’t get created at initialization stage. The interpreter won’t see it:alert("b" in window) // false, there is no window.b alert(b) // error, b is not defined b = 5
But after the assignment,
b
becomes the regular variablewindow.b
as if it were declared:b = 5 alert("b" in window) // true, there is window.b = 5
- At initialization stage,
window.a
is created:
// window = {a:undefined} if ("a" in window) { var a = 1 } alert(a)
"a" in window
istrue
.
// window = {a:undefined} if (true) { var a = 1 } alert(a)
So,
if
is executed and hence value ofa
becomes1
.- When the interpreter is preparing to start function code execution, before the first line is run, an empty
LexicalEnvironment
is created and populated with arguments, local variables and nested functions.function sayHi(name) { // LexicalEnvironment = { name: 'John', phrase: undefined } var phrase = "Hi, " + name alert(phrase) } sayHi('John')
Naturally, arguments have the starting value, but the local variables don’t.
- Then the function code runs, eventually assignments are executed.
A variable assignment internally means that the corresponding property of the
LexicalEnvironment
gets a new value.So,
phrase = "Hi, "+name
changes theLexicalEnvironment
:function sayHi(name) { // LexicalEnvironment = { name: 'John', phrase: undefined } var phrase = "Hi, " + name // LexicalEnvironment = { name: 'John', phrase: 'Hi, John'} alert(phrase) } sayHi('John')
The last line
alert(phrase)
searches thephrase
inLexicalEnvironment
and outputs it’s value. - At the end of execution, the
LexicalEnvironment
is usually junked with all its contents, because the variables are no longer needed. But (as we’ll see) it’s not always like that.
What will be the result?
if ("a" in window) { var a = 1 } alert(a)
The answer is 1
.
Let’s trace the code to see why.
What will be the result (no var
before a
)?
if ("a" in window) { a = 1 } alert(a)
The answer is “Error: no such variable”, because there is no variable a
at the time of "a" in window
check.
So, the if
branch does not execute and there is no a
at the time of alert
.
Function variables
When the function runs, on every function call, the new LexicalEnvironment
is created and populated with arguments, variables and nested function declarations.
This object is used internally to read/write variables. Unlike window
, the LexicalEnvironment
of a function is not available for direct access.
Let’s consider the details of execution for the following function:
function sayHi(name) { var phrase = "Hi, " + name alert(phrase) } sayHi('John')
If we look into the recent ECMA-262 specification, there are actually two objects.
The first is a VariableEnvironment
object, which is actually populated by variables and functions, declared by FunctionDeclaration
, and then becomes immutable.
The second is a LexicalEnvironment
object, which is almost same as VariableEnvironment
, but it is actually used during the execution.
A more formal description can be found in the ECMA-262 standard, sections 10.2-10.5 and 13.
It is also noted that in JavaScript implementations, these two objects can be merged into one. So, we evade irrelevant details and use the term LexicalEnvironment
everywhere.
Blocks do not have scope
There is no difference between the following:
var i = 1 { i = 5 }
…And the following
i = 1 { var i = 5 }
All var
declarations are processed before the execution in in both cases.
Unlike languages like Java, C etc, variables in JavaScript survive after a loop.
That’s again, because their scope is a function.
for(var i=0; i<5; i++) { } alert(i) // 5, variable survives and keeps value
Declaring a variable in the loop is convenient, but doesn’t make the loop it’s scope.
What this test is going to alert? Why? (Don’t run it before you answer, please)
function test() { alert(window) var window = 5 } test()
The var
directive is processed on the pre-execution stage.
So, window
becomes a local variable before it comes to alert:
LexicalEnvironment = { window: undefined }
So when the execution starts and reaches first line, variable window
exists and is undefined.
How do you think, what will be the output? Why?
var value = 0 function f() { if (1) { value = 'yes' } else { var value = 'no' } alert(value) } f()
The var
directive is processed and created as LexicalEnvironment
property at pre-execution stage.
So, the line value='yes'
performs an assignment to the local variable, and the last alert outputs 'yes'
.