In-depth understanding of the JavaScript series (14): Scope chain (Scope Chain)

Source: Internet
Author: User
Tags closure
Preface
In Chapter 12, we have known that the data of an execution context (variables, function declarations and function parameters) are stored in the variable object as attributes.
At the same time, we also know that the variable object is created every time it enters the context, and the initial value is filled in. The update of the value occurs in the code execution stage.
This chapter is devoted to more details that are directly related to the execution context, and this time we will refer to the topic of scope chain.
Original English: http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/
Chinese reference: http://www.denisdeng.com/? P = 908
Most of the content of this article comes from the above address, with only a few modifications. Thank you
Definition
If you want to briefly describe and highlight it, scope chains are mostly related to internal functions.
We know that ECMAScript allows the creation of intrinsic functions, and we can even return them 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 the function, it is the active object.
The scope chain is exactly the list of all variable objects (including parent variable objects) in the internal context. This chain is used for variable queries. In the above example, the scope chain of the "bar" context includes Ao (bar), Ao (foo), and VO (global).
But let's look at it carefully.
Let's start with the definition and take a deeper look at the examples.
The scope chain is context dependent and the chain of variable objects is used to find variables in identifier resolution.
The scope chain of the function context is created when the function is called. It contains the active object and the [[scope]] attribute inside the function. Next, we will discuss the [[scope]] property of a function in more detail.
In the context, it is illustrated as follows:
activeExecutionContext = {
VO: {...}, // or AO
this: thisValue,
Scope: [ // Scope chain
//List of all variable objects
// for identifiers lookup
]
}
Its scope is defined as follows:
Scope = AO + [[Scope]]
This association and identifier resolution process, which we will discuss below, is related to the life cycle of a function.
The life cycle of a function
The life cycle of a function is divided into the creation and activation phases (when called). Let's examine it in detail.
Function creation
As we all know, function declarations are placed in variable / activity (VO / AO) objects when entering context. Let's look at the variables and function declarations in the global context (here the variable object is the global object itself, we remember, right?)
Var x = 10;
function foo() {
Var y = 20;
alert(x + y);
}
Foo (); / / 30
When the function is activated, we get the correct result - 30. However, there is a very important feature.
Previously, we only talked about variable objects in the current context. Here, we see that the variable "Y" is defined in the function "foo" (meaning it is in Ao of foo context), but the variable "X" is not defined in "foo" context, and accordingly, it will not be added to Ao of "foo". At first glance, the variable "X" does not exist at all with respect to the function "foo"; but as we can see below - at a glance only - we find that the active object in the context of "foo" contains only one attribute - "Y".
fooContext.AO = {
y: Undefined / / undefined - 20 – at activation when entering context
}
How does function "foo" access variable "X"? In theory, a function should be able to access a variable object with a higher context. In fact, this mechanism is implemented through the [[scope]] attribute inside the function.
[[scope]] is the hierarchical chain of all parent variable objects, which is above the current function context and is stored in it when the function is created.
Note that this important point [[scope]] is stored when the function is created - static (immutable), forever, until the function is destroyed. That is, the function can never be called, but the [[scope]] property has been written and stored in the function object.
Another thing to consider - in contrast to scope chains, [[scope]] is an attribute of a function rather than a context. Considering the above example, the [[scope]] of the function "foo" is as follows:
foo.[[Scope]] = [
globalContext.VO // === Global
];
For example, we use the usual ECMAScript array to represent the scope and [[scope]].
Continue, we know that the context is entered when the function is called, when the active object is created, and this and scope (scope chain) are determined. Let's think about 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 lookup) is defined as follows:
Scope = AO|VO + [[Scope]]
The above code means that the active object is the first object of the scope array, which 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 a process used to determine 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), property name group

In the return value of this algorithm, we always have a reference type. Its base component is the corresponding variable object (or null if it is not found), and the property name component is the name of the identifier searched upward. Details of reference types are discussed in Chapter 13. This.
The identifier resolution process includes the search of attributes corresponding to variable names, that is, the continuous search of variable objects in the scope, starting from the deepest context, bypassing the scope chain to the top.
As a result, local variables in a context have a higher priority than variables in the parent scope in an up lookup. If one or two variables have the same name but come from different scopes, the first one to be found is in the deepest scope.
Let's use a slightly more complex example to describe the 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 / active objects, the [[scope]] attribute of the function, and the scope chain of the context:
The variable objects of the global context are:
globalContext.VO === Global = {
X: 10
foo: <reference to function>
}
When "foo" is created, the [[scope]] attribute of "foo" is:
foo.[[Scope]] = [
globalContext.VO
];
When "foo" is activated (entering context), the active objects of "foo" context are:
fooContext.AO = {
Y: 20,
bar: <reference to function>
}
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 objects of the bar context are:
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", "Z" are resolved 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 the [[scope]] properties of functions.
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 co exists with the function. In fact, a closure is a combination of function code and its [[scope]]. Therefore, as one of its objects, [[scope]] includes the lexical scope (parent variable object) created within the function. When the function is further activated, variables from the higher scope will be searched in the lexical chain of the variable object (stored statically at creation time).
For example:
Var x = 10;
function foo() {
Alert (x);
}
(function () {
Var x = 20;
foo(); // 10, but not 20
} ();
Again, we see that in the identifier parsing process, the lexical scope defined when the function was created -- the variable resolved to 10 instead of 30. In addition, this example also clearly shows that [[scope]] of a function (in this case, an anonymous function returned from the function "foo") persists even after the scope created by the function has been completed.
For more details on the theory and implementation mechanism of closures in ECMAScript, read Chapter 16 closures.
[[scope]] of a function created through a constructor
In the above example, we see that the [[scope]] property of the function is obtained when the function is created, through which all variables of the parent context are accessed. However, there is an important exception to this rule, which 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 by the function constructor cannot access the variable "Y". But that doesn't mean the function "barfn" doesn't have a [[scope]] attribute (otherwise it can't access the variable "X"). The problem is that the [[scope]] property of a function created by a function constructor is always a unique global object. With this in mind, it is impossible to create a top-level context closure other than the global through such a function.
2D scope chain lookup
The most important thing to look up in the scope chain is that the properties (if any) of the variable object must take into account the prototype features of ECMAScript. If an attribute is not found directly in the object, the query continues in the prototype chain. That is to say, two-dimensional chain search. (1) Scope chain links; (2) each scope chain - drill down to prototype chain links. We can see this effect if the attribute is defined in object.prototype.
function foo() {
Alert (x);
}
Object.prototype.x = 10;
Foo (); / / 10
There is no prototype for the active object, as we can see in the following example:
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 resolved in object.prototype because it is not directly resolved in Ao. But in the first example above, in identifier parsing, we reach the global object (not all of which are in some implementations), which inherits from object.prototype, and in response, "X" resolves to 10.
The same situation occurs in some versions of spidermokey named function expressions (NFE for short), where specific objects store the optional names of function expressions inherited from object.prototype. In some versions of blackberry, the active objects inherit from object.prototype during execution. However, more details about this feature are discussed in the functions in Chapter 15.
Scope chain in the context of global and eval
It's not necessarily interesting here, but it's a reminder. The scope chain of the global context 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;
Influence of code execution on scope chain
In ECMAScript, there are two declarations that modify the scope chain during code execution. This is the with declaration and the catch statement. They are added to the forefront of the scope chain, and the object must be looked up in the identifier that appears in these declarations. If one of them occurs, the scope chain is briefly modified as follows:
Scope = withObject|catchObject + AO|VO + [[Scope]]
In this example, add an object whose parameters are the object (so that, without a prefix, the object's properties become accessible).
var foo = {x: 10, y: 20};
With (foo) {
alert(x); // 10
alert(y); // 20
}
The scope chain is modified as follows:
Scope = foo + AO|VO + [[Scope]]
Again, we see that through the with statement, the resolution of the object identifier 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 you enter the context? Identifiers' x 'and' y 'have been added to the variable object. In addition, the following modifications are made during the code operation phase:
x = 10, y = 10;
Object {X: 20} is added to the front of the scope;
In with, the VaR declaration 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 resolved and added to the front end of the scope chain. The "X" is 20 and becomes 30;
There is also the modification of variable object "Y", whose value changes from 10 to 30 after being parsed;
In addition, after the with 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 is strengthened.
In the last two alerts, the "X" of the current variable object remains the same, and the "Y" value is now equal to 30, which has changed in the with declaration run.
Similarly, the exception parameter of the catch statement becomes accessible, creating a new object with only one property, the exception parameter name. The diagram looks like this:
Try {
...
} catch (ex) {
Alert (Ex);
}
The scope chain is modified to:
var catchObject = {
ex: <exception object>
}
Scope = catchObject + AO|VO + [[Scope]]
After the catch statement finishes running, the scope chain returns to its previous state.
conclusion
At this stage, we consider almost all the common concepts related to execution context, as well as the details related to them. According to the detailed analysis of plan function object: function type (function declaration, function expression) and closure. By the way, in this article, closures are directly related to the [[scope]] attribute, but about it will be discussed in the appropriate chapter. I'd be happy to answer your questions in the comments.
Other reference
8.6.2 – [[Scope]]
10.1.4 – Scope Chain and Identifier Resolution
Synchronization and recommendation
This article has been synced to the directory index: an in-depth understanding of JavaScript series
In depth understanding of JavaScript series of articles, including original, translation, reprint and other types of articles, if it is useful for you, please recommend and support one to give uncle the power of writing.


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.