Always in programs we declare functions and variables which then successfully use building our systems. But how and where the interpreter finds our data (functions, variable)? What occurs, when we reference to needed objects?
Many ECMAScript programmers know that variables are closely related with the execution context:
var
a = 10;
// variable of the global context
(
function
() {
var
b = 20;
// local variable of the function context
})();
alert(a);
// 10
alert(b);
// "b" is not defined
|
Also, many programmers know that the isolated scope in the current version of specification is created only by execution contexts with “function” code type. I.e., in contrast with C/C++, for example the block of for
loop in ECMAScript does not create a local context:
for
(
var
k
in
{a: 1, b: 2}) {
alert(k);
}
alert(k);
// variable "k" still in scope even the loop is finished
|
Let’s see in more details what occurs when we declare our data.
If variables are related with the execution context, it should know where its data are stored and how to get them. This mechanism is called a variable object.
var
, VariableDeclaration);declared in the context.
Notice, in ES5 the concept of variable object is replaced with lexical environments model, which detailed description can be found in appropriate chapter.
Schematically and for examples, it is possible to present variable object as a normal ECMAScript object:
VO = {};
|
And as we said, VO is a property of an execution context:
activeExecutionContext = {
VO: {
// context data (var, FD, function arguments)
}
};
|
Indirect referencing to variables (via property names of VO) allows only variable object of the global context (where the global object is itself the variable object). For other contexts directly to reference the VO is not possible, it is purely mechanism of implementation.
When we declare a variable or a function, there is nothing else as creation of the new property of the VO with the name and value of our variable.
Example:
var
a = 10;
function
test(x) {
var
b = 20;
};
test(30);
|
And corresponding variable objects are:
// Variable object of the global context
VO(globalContext) = {
a: 10,
test: <reference to
function
>
};
// Variable object of the "test" function context
VO(test functionContext) = {
x: 30,
b: 20
};
|
But at implementation level (and specification) the variable object is an abstract essence. Physically, in concrete execution contexts, VO is named differently and has different initial structure.
Some operations (e.g. variable instantiation) and behavior of the variable object are common for all execution context types. From this viewpoint it is convenient to present the variable object as an abstract base thing. Function context can also define additional details related with the variable object.
AbstractVO (generic behavior of the variable instantiation process)
║
╠══> GlobalContextVO
║ (VO ===
this
=== global)
║
╚══> FunctionContextVO
(VO === AO, <arguments> object and <formal parameters> are added)
|
Let’s consider it in detail.
Here, first it is necessary to give definition of the Global object.
Global object is the object which is created before entering any execution context; this object exists in the single copy, its properties are accessible from any place of the program, the life cycle of the global object ends with program end.
At creation the global object is initialized with such properties as Math
, String
, Date
, parseInt
etc., and also by additional objects among which can be the reference to the global object itself — for example, in BOM, window
property of the global object refers to global object (however, not in all implementations):
global = {
Math: <...>,
String
: <...>
...
...
window
: global
};
|
When referencing to properties of global object the prefix is usually omitted, because global object is not accessible directly by name. However, to get access to it is possible via this value in the global context, and also through recursive references to itself, for example window
in BOM, therefore write simply:
String
(10);
// means global.String(10);
// with prefixes
window
.a = 10;
// === global.window.a = 10 === global.a = 10;
this
.b = 20;
// global.b = 20;
|
So, coming back to variable object of the global context — here variable object is the global object itself:
VO(globalContext) === global;
|
It is necessary to understand accurately this fact since for this reason declaring a variable in the global context, we have ability to reference it indirectly via property of the global object (for example when the variable name is unknown in advance):
var
a =
new
String
(
'test'
);
alert(a);
// directly, is found in VO(globalContext): "test"
alert(
window
[
'a'
]);
// indirectly via global === VO(globalContext): "test"
alert(a ===
this
.a);
// true
var
aKey =
'a'
;
alert(
window
[aKey]);
// indirectly, with dynamic property name: "test"
|
Regarding the execution context of functions — there VO is inaccessible directly, and its role plays so-called an activation object (in abbreviated form — AO).
VO(functionContext) === AO;
|
An activation object is created on entering the context of a function and initialized by propertyarguments
which value is the Arguments object:
AO = {
arguments: <ArgO>
};
|
Arguments object is a property of the activation object. It contains the following properties:
Example:
function
foo(x, y, z) {
// quantity of defined function arguments (x, y, z)
alert(foo.length);
// 3
// quantity of really passed arguments (only x, y)
alert(arguments.length);
// 2
// reference of a function to itself
alert(arguments.callee === foo);
// true
// parameters sharing
alert(x === arguments[0]);
// true
alert(x);
// 10
arguments[0] = 20;
alert(x);
// 20
x = 30;
alert(arguments[0]);
// 30
// however, for not passed argument z,
// related index-property of the arguments
// object is not shared
z = 40;
alert(arguments[2]);
// undefined
arguments[2] = 50;
alert(z);
// 40
}
foo(10, 20);
|
Concerning the last case, in older versions of Google Chrome there was a bug — there parameterz
and arguments[2]
were also shared.
In ES5 the concept of activation object is also replaced with common and single model oflexical environments.
Now we have reached the main point of this article. Processing of the execution context code is divided on two basic stages:
Modifications of the variable object are closely related with these two phases.
Notice, that processing of these two stages are the general behavior and independent from the type of the context (i.e. it is fair for both: global and function contexts).
On entering the execution context (but before the code execution), VO is filled with the following properties (they have already been described at the beginning):
— a property of the variable object with a name and value of formal parameter is created; for not passed parameters — property of VO with a name of formal parameter and value undefinedis created;
— a property of the variable object with a name and value of a function-object is created; if the variable object already contains a property with the same name, replace its value and attributes;
— a property of the variable object with a variable name and value undefined is created; if the variable name is the same as a name of already declared formal parameter or a function, the variable declaration does not disturb the existing property.
Let’s see on the example:
function
test(a, b) {
var
c = 10;
function
d() {}
var
e =
function
_e() {};
(
function
x() {});
}
test(10);
// call
|
On entering the test
function context with the passed parameter 10
, AO is the following:
AO(test) = {
a: 10,
b: undefined,
c: undefined,
d: <reference to FunctionDeclaration
"d"
>
e: undefined
};
|
Notice, that AO does not contain function x
. This is because x
is not a function declaration but thefunction-expression (FunctionExpression, in abbreviated form FE) which does not affect on VO.
However, function _e
is also a function-expression, but as we will see below, because of assigning it to the variable e
, it becomes accessible via the e
name. The difference of a FunctionDeclaration
from the FunctionExpression
is in detail discussed in Chapter 5. Functions.
And after that there comes the second phase of processing of a context code — the code executionstage.
By this moment, AO/VO is already filled by properties (though, not all of them have the real values passed by us, most of them yet still have initial value undefined
).
Considering all the same example, AO/VO during the code interpretation is modified as follows:
AO[
'c'
] = 10;
AO[
'e'
] = <reference to FunctionExpression
"_e"
>;
|
Once again I notice that function expression _e
is still in memory only because it is saved to declared variable e
. But the function expression x
is not in the AO/VO. If we try to call x
function before oreven after definition, we get an error: "x" is not defined
. Unsaved to a variable function expression can be called only with its definition (in place) or recursively.
One more (classical) example:
alert(x);
// function
var
x = 10;
alert(x);
// 10
x = 20;
function
x() {};
alert(x);
// 20
|
Why in the first alert x
is a function and moreover is accessible before the declaration? Why it’s not10
or 20
? Because, according to the rule — VO is filled with function declarations on entering the context. Also, at the same phase, on entering the context, there is a variable declaration x
, but as we mentioned above, the step of variable declarations semantically goes after function and formal parameters declarations and on this phase do not disturb the value of the already declared function or formal parameter with the same name. Therefore, on entering the context VO is filled as follows:
VO = {};
VO[
'x'
] = <reference to FunctionDeclaration
"x"
>
// found var x = 10;
// if function "x" would not be already defined
// then "x" be undefined, but in our case
// variable declaration does not disturb
// the value of the function with the same name
VO[
'x'
] = <the value is not disturbed, still
function
>
|
And then at code execution phase, VO is modified as follows:
VO[
'x'
] = 10;
VO[
'x'
] = 20;
|
what we can see in the second and third alerts.
In the example below we see again that variables are put into the VO on entering the context phase (so, the else
block is never executed, but nevertheless, the variable b
exists in VO):
if
(
true
) {
var
a = 1;
}
else
{
var
b = 2;
}
alert(a);
// 1
alert(b);
// undefined, but not "b is not defined"
|
Often various articles and even books on JavaScript claim that: “it is possible to declare global variables using var keyword (in the global context) and without using var
keyword (in any place)”. It is not so. Remember:
variables are declared only with using var keyword.
And assignments like:
a = 10;
|
just create the new property (but not the variable) of the global object. “Not the variable” is not in the sense that it cannot be changed, but “not the variable” in concept of variables in ECMAScript (which then also become properties of the global object because of VO(globalContext) === global, we remember, yeah?).
And the difference is the following (let’s show on the example):
alert(a);
// undefined
alert(b);
// "b" is not defined
b = 10;
var
a = 20;
|
All again depends on VO and phases of its modifications (entering the context stage and the code execution stage):
Entering the context:
VO = {
a: undefined
};
|
We see that at this phase there is no any b
since it is not a variable, b
will appear only at code execution phase (but in our case won’t since there is an error).
Let’s change the code:
alert(a);
// undefined, we know why
b = 10;
alert(b);
// 10, created at code execution
var
a = 20;
alert(a);
// 20, modified at code execution
|
There is one more important point concerning variables. Variables, in contrast with simple properties, have attribute {DontDelete}
, meaning impossibility to remove a variable via the delete
operator:
a = 10;
alert(
window
.a);
// 10
alert(
|