Preface
In the description of variable objects in Chapter 12, we already know that the data of an execution context (variables, function declarations and function parameters) are stored in variable objects as attributes.
At the same time, we also know that the variable object is created every time it enters the context and filled in with the initial value. The value update occurs during the code execution phase.
This chapter is dedicated to discussing more details directly related to execution context, and this time we will mention a topic - scope chain.
English original text: http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/
Chinese reference: http://www.denisdeng.com/?p=908
This article is huge Part of the content comes from the above address, with only minor modifications. Thanks to the author
Definition
If you want to briefly describe and show its key points, then the scope chain is mostly related to internal functions.
We know that ECMAScript allows the creation of internal functions, and we can even return these functions from the parent function.
var x = 10;
function foo() {
var y = 20;
function bar() {
alert(x y);
}
return bar;
}
foo()(); // 30
In this way, it is obvious that each context has its own variable object: for the global context, it is the global object itself; for functions, it is the active object.
The scope chain is just the list of all variable objects (including parent variable objects) in the internal context. This chain is used for variable query. That is, in the above example, the scope chain of the "bar" context includes AO(bar), AO(foo), and VO(global).
But let’s take a closer look at this issue.
Let’s start with the definition and discuss examples in more depth.
A scope chain is associated with an execution context, and the chain of variable objects is used for variable lookup in identifier resolution.
The scope chain of the function context is created when the function is called, including the active object and the [[scope]] attribute inside this function. Below we discuss the [[scope]] attribute of a function in more detail.
is represented in context as follows:
activeExecutionContext = {
VO: {...}, // or AO
this: thisValue,
Scope: [ // Scope chain
// List of all variable objects
// for identifiers lookup
]
};
The scope is defined as follows:
Scope = AO [[Scope]]
This union and identifier resolution process, we will discuss below Discussion, this is related to the life cycle of the function.
Life cycle of a function
The life cycle of a function is divided into creation and activation phases (when called), let us study it in detail.
Function Creation
As we all know, function declarations are placed in Variable/Activity (VO/AO) objects when entering the context. Let’s look at variable and function declarations in the global context (here the variable object is the global object itself, we remember that, right?)
var x = 10;
function foo() {
var y = 20;
alert(x y);
}
foo(); // 30
When the function is activated, we get the correct (expected) result - 30. However, there is one very important feature.
Previously, we only talked about variable objects related to the current context. Here, we see that the variable "y" is defined in the function "foo" (meaning it is in the AO of the foo context), but the variable "x" is not defined in the "foo" context, and accordingly, it does not Added to the AO of "foo". At first glance, the variable "x" does not exist at all relative to the function "foo"; but as we see below - and only at a "glance", we find that the active object of the "foo" context only contains one Attribute - "y".
fooContext.AO = {
y: undefined / / undefined – 20 when entering context – at activation
};
How does function "foo" access variable "x"? In theory, the function should be able to access the variable object of a higher-level context. In fact, it is exactly like this. This mechanism is implemented through the [[scope]] attribute inside the function.
[[scope]] is a hierarchical chain of all parent variable objects, above the current function context, and stored in it when the function is created.
Note this important point - [[scope]] is stored when the function is created - static (immutable), forever and ever, until the function is destroyed. That is: the function can never be called, but the [[scope]] attribute has been written and stored in the function object.
Another thing to consider is that in contrast to scope chains, [[scope]] is an attribute of the function rather than the context. Considering the above example, the [[scope]] of function "foo" is as follows:
foo.[[Scope]] = [
globalContext.VO // === Global
];
For example, we use the usual ECMAScript Arrays represent scopes and [[scope]].
Continuing, we know that the context is entered when the function is called. At this time, the active object is created, and this and the scope (scope chain) are determined. Let us consider this moment in detail.
Function activation
As mentioned in the definition, after entering the context to create AO/VO, the Scope attribute of the context (a scope chain for variable search) is defined as follows:
Scope = AO|VO [ [Scope]]
The meaning of the above code is: the active object is the first object of the scope array, that is, added to the front end of the scope.
Scope = [AO].concat([[Scope]]);
This feature is very important for the processing of identifier parsing.
Identifier resolution is the process of determining which variable object a variable (or function declaration) belongs to.
In the return value of this algorithm, we always have a reference type, its base component is the corresponding variable object (or null if not found), and the attribute name component is the name of the identifier looked up. Details of reference types are discussed in Chapter 13.this.
The identifier resolution process consists of a lookup of attributes corresponding to variable names, that is, a continuous lookup of variable objects in the scope, starting from the deepest context and bypassing the scope chain to the top.
In this way, in upward search, local variables in a context have higher priority than variables in the parent scope. In case two variables have the same name but come from different scopes, the first one found is in the deepest scope.
We use a slightly more complicated example to describe what we have mentioned above.
var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
alert(x y z);
}
bar();
}
foo(); // 60
For this, we have the following variables/activities, the [[scope]] attribute of the function and the scope chain of the context:
Global context The variable object is:
globalContext.VO === Global = {
x: 10
foo:
};
The [[scope]] attribute of "foo" when "foo" is created Is:
foo.[[Scope]] = [
globalContext.VO
];
When "foo" is activated (enters the context), the active object of the "foo" context is:
fooContext.AO = {
y: 20,
bar:
};
The scope chain of the "foo" context is:
fooContext.Scope = fooContext.AO foo.[[Scope]] // i.e.:
fooContext.Scope = [
fooContext.AO,
globalContext. VO
];
When the internal function "bar" is created, its [[scope]] is:
bar.[[Scope]] = [
fooContext.AO,
globalContext.VO
];
When "bar" is activated, " The active object of the bar" context is:
barContext.AO = {
z: 30
};
The scope chain of the "bar" context is:
barContext.Scope = barContext.AO bar.[[Scope]] // i.e.:
barContext.Scope = [
barContext. AO,
fooContext.AO,
globalContext.VO
];
The identifiers of "x", "y" and "z" are parsed as follows:
- "x"
-- barContext.AO // not found
-- fooContext.AO // not found
-- globalContext.VO // found - 10
- "y"
-- barContext.AO // not found
-- fooContext.AO // found - 20
- "z"
-- barContext.AO // found - 30
Scope characteristics
Let’s look at some important features related to scope chains and function [[scope]] attributes.
Closure
In ECMAScript, closures are directly related to the [[scope]] of a function. As we mentioned, [[scope]] is stored when the function is created and will live and die with the function. In fact, a closure is the combination of the function code and its [[scope]]. Therefore, [[Scope]] includes as one of its objects the lexical scope (parent variable object) created within the function. When the function is further activated, within this lexical chain of variable objects (which are stored statically at creation time), variables from higher scopes will be searched.
For example:
var x = 10;
function foo() {
alert(x);
}
(function () {
var x = 20;
foo(); // 10, but not 20
})();
We see again that during identifier resolution, the lexical scope defined when the function was created is used - the variable resolves to 10 instead of 30. Furthermore, this example also clearly shows that the [[scope]] of a function (in this case an anonymous function returned from function "foo") persists even after the scope created by the function has been completed.
For more details on the theory of closures and their execution mechanisms in ECMAScript, read Chapter 16 Closures.
The [[scope]] of the function created through the constructor
In the above example, we see that the [[scope]] attribute of the function is obtained when the function is created, and all parent contexts are accessed through this attribute variable. However, there is an important exception to this rule, and it involves functions created through function constructors.
var x = 10;
function foo() {
var y = 20;
function barFD() { // Function declaration
alert(x);
alert(y);
}
var barFE = function () { // Function expression
alert(x);
alert(y);
};
var barFn = Function('alert(x); alert(y);');
barFD(); // 10, 20
barFE(); // 10, 20
barFn(); // 10, "y" is not defined
}
foo() ;
We see that the function "bar" created through the function constructor cannot access the variable "y". But this does not mean that the function "barFn" does not have the [[scope]] attribute (otherwise it cannot access the variable "x"). The problem is that the [[scope]] attribute of a function created via the function constructor is always the only global object. With this in mind, it is not possible to create a top-level context closure other than the global one via such a function.
Two-dimensional scope chain search
The most important point in searching in the scope chain is that the attributes of the variable object (if any) must be taken into account - derived from the prototype feature of ECMAScript. If a property is not found directly in the object, the query continues in the prototype chain. That is often called two-dimensional chain search. (1) Scope chain link; (2) Each scope chain - going deep into the prototype chain link. We can see this effect if the properties are defined in Object.prototype.
function foo() {
alert(x) ;
}
Object.prototype.x = 10;
foo(); // 10
Active objects have no prototype, as we can see in the example below:
function foo() {
var x = 20;
function bar() {
alert(x);
}
bar();
}
Object.prototype.x = 10;
foo(); // 20
If the activation object of the function "bar" context has a prototype, then "x" will be in Object .prototype because it is not directly parsed in AO. But in the first example above, during identifier resolution, we reach the global object (not always the case in some implementations), which inherits from Object.prototype, and accordingly, "x" resolves to 10.
The same situation occurs with named function expressions (abbreviated NFE) in some versions of SpiderMokey, where a specific object stores the optional name of a function expression inherited from Object.prototype, in Blackberry In some versions, the execution-time activation object inherits from Object.prototype. However, more details about this feature are discussed in Chapter 15 Functions.
Scope chains in global and eval contexts
Not necessarily interesting here, but a reminder. The global context's scope chain contains only global objects. The context of code eval has the same scope chain as the current calling context.
globalContext.Scope = [
Global
];
evalContext.Scope === callingContext.Scope;
The impact on the scope chain during code execution
In ECMAScript, there are two statements that can be modified during the code execution phase scope chain. This is the with statement and catch statement. They are added to the front of the scope chain, and objects must be looked up among the identifiers that appear in these declarations. If one of these occurs, the scope chain is briefly modified as follows:
Scope = withObject|catchObject AO|VO [[Scope]]
In this example, the object is added as its parameter (so, no prefix, the properties of this object become accessible).
var foo = {x: 10, y: 20} ;
with (foo) {
alert(x); // 10
alert(y); // 20
}
The scope chain is modified like this :
Scope = foo AO|VO [[Scope]]
We see again that through the with statement, the resolution of the identifier in the object is added to the front of the scope chain:
var x = 10, y = 10;
with ({x: 20}) {
var x = 30, y = 30;
alert(x); // 30
alert(y); // 30
}
alert(x); // 10
alert(y); // 30
What happens when entering context? The identifiers "x" and "y" have been added to the variable object. In addition, the following modifications are made during the code running phase:
x = 10, y = 10;
The object {x:20} is added to the front end of the scope;
Inside with, a var statement is encountered, of course Nothing is created because when entering the context, all variables have been parsed and added;
In the second step, only the variable "x" is modified, in fact the "x" in the object is now parsed and added to the role At the front end of the domain chain, "x" is 20 and changes to 30;
There is also a modification to the variable object "y", and its value changes from 10 to 30 after being parsed;
In addition, in with After the declaration is completed, its specific object is removed from the scope chain (the changed variable "x" - 30 is also removed from that object), that is, the structure of the scope chain is restored to the state before with was strengthened.
In the last two alerts, the "x" of the current variable object remains the same, and the value of "y" is now equal to 30, which has changed during the with statement run.
Similarly, the exception parameter of the catch statement becomes accessible, which creates a new object with only one attribute - the exception parameter name. The illustration looks like this:
try {
...
} catch (ex) {
alert(ex);
}
The scope chain is modified to:
var catchObject = {
ex:
};
Scope = catchObject AO|VO [[Scope]]
After the catch statement completes running, the scope chain is restored to its previous state.
Conclusion
At this stage, we have considered almost all the common concepts related to execution context, and the details related to them. As planned - detailed analysis of function objects: function types (function declarations, function expressions) and closures. By the way, in this article, closures are directly related to the [[scope]] attribute, but that will be discussed in the appropriate chapter. I'd be happy to answer your questions in the comments.
Other references