preface

We all know that the data of an execution context (variables, function declarations, and function parameters) are stored as properties in variable objects, but we should also know that variable objects are created and filled with initial values each time they enter the context, and that values are updated during code execution. So let’s focus on more details directly related to the execution context, and this time we’ll cover one topic — scope chains.

English text: http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/ Chinese reference: http://www.denisdeng.com/?p=908 for the most part this article content from the above address, only to do a little modification, thanks to the author If you have the same, is copied, woo hooCopy the code

define

To briefly describe and illustrate the point, the scope chain is mostly related to internal functions.

As we know,ECMAScript allows you to create internal functions, and we can even return them from parent functions.

var x = 10
function foo () {
    var y = 20
    function bar () {
        alert(x + y)
    }
    return bar
}

foo()() // 30
Copy the code

Thus, 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 live object.

A scope chain is simply a list of all variable objects (including parent variable objects) in the internal context used for variable queries. In the example above, the scope of the “bar” context includes AO(foo), AO(foo), and VO(global).

But let’s talk about this in detail.

Let’s start with the definitions and discuss further examples.

The scope chain is associated with an execution context, and the chain of variable objects is used for variable lookups in identifier resolution

The scope chain of a function context is created when the function is called, and contains the active object and the [scope] property inside the function. Let’s discuss a function [scope] property in more detail.

The following is indicated in context:

activeExecutionContext = { VO: {... }, // or AO this: thisValue, Scope: [// Scope chain // list of variable objects //for identifiers lookup
    ]
};
Copy the code

Its scope is defined as follows:

Scope = AO + [[Scope]]
Copy the code

This union and identifier interpretation process, which we discuss below, is related to the declaration cycle of functions.


The life cycle of a function

The life cycle of a function is divided into the creation and activation phases (when invoked), so let’s examine this in detail

Function creates

As we all know, function declarations are placed in variable/activity (VO/AO) objects when entering context. Let’s look at variable and function declarations in the global context (here the variable object is the global object itself, remember, right?).

var x = 10;
 
function foo() { var y = 20; alert(x + y); } foo(); / / 30Copy the code

When the function is activated, we get the correct (expected) result –30. However, there is a very important feature.

Previously, we only talked about variable objects that are relevant to the current context. Here, we see that the variable “y” is defined in function “foo” (meaning it is in the AO of foo’s context), but the variable “x” is not defined in the context of “foo” and, accordingly, it is not added to the AO of “foo”. At first glance, the variable “x” relative to the function “foo” doesn’t exist at all; But as we’ll see below — and only at a glance — we see that the live object of the “foo” context contains only one property, “y.”

Foocontext. AO = {y: undefined // undefined - at activation};Copy the code

How does function “foo” access variable “x”? Theoretically a function should be able to access a variable object in a higher context. In fact, this mechanism is implemented through the [[scope]] property inside the function, which is a hierarchical chain of all parent variable objects above the context of the current function and stored in it when the function is created.

Note the important point that [[scope]] is stored when the function is created, forever and ever, until the function is destroyed, i.e., the function may never be called, but the [[scope]] property has been written and stored in the function object.

Another consideration is that, in contrast to scope chains, [[scope]] is an attribute of a function, not a context. Considering the above example, the [[scope]] of function “foo” looks like this:

foo.[[Scope]] = [
  globalContext.VO // === Global
];
Copy the code

For example, we represent scopes and [[scope]] with a normal ECMAScript array. Further, we know that the context is entered at function call time, when the active object is created and the this and scope (scope chain) are determined. Let us consider this moment in detail.

Function is activated

As stated in the definition, after entering the context to create AO/VO, the context’s Scope property (a chain of scopes for the variable lookup) is defined as follows:

Scope = AO|VO + [[Scope]]
Copy the code

The active object is the first object in the scope array, added to the front of the scope.

Scope = [AO].concat([[Scope]]);
Copy the code

This feature is important for handling identifier parsing

Identifier resolution is the 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 whose base component is the corresponding variable object (or null if not found) and whose attribute name component is the name of the identifier to look up. The identifier resolution process involves lookups of attributes corresponding to variable names, that is, continuous lookups of variable objects in scope, starting in the deepest context and working up the scope chain to the top. As a result, local variables in a context have higher precedence over variables in the parent scope when looking up. In the event that two variables have the same name but come from different scopes, the first to be found is in the deepest scope.

Let’s use a slightly more complicated example to describe all of this.

var x = 10;
 
function foo() {
  var y = 20;
 
  function bar() { var z = 30; alert(x + y + z); } bar(); } foo(); / / 60Copy the code

For this, we have the following variables/active objects, the function’s [[scope]] property, and the context’s scope chain: the global context variable object is:

globalContext.VO === Global = {
  x: 10
  foo: <reference to function>};Copy the code

When “foo” is created, the [[scope]] property of “foo” is:

foo.[[Scope]] = [
  globalContext.VO
];
Copy the code

When “foo” is activated (into context), the active objects of the “foo” context are:

fooContext.AO = {
  y: 20,
  bar: <reference to function>};Copy the code

The scope chain of the “foo” context is:

fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:
 
fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];
Copy the code

When the inner function “bar” is created, its [[scope]] is:

bar.[[Scope]] = [
  fooContext.AO,
  globalContext.VO
];
Copy the code

When “bar” is activated, the active objects of the “bar” context are:

barContext.AO = {
  z: 30
};
Copy the code

The scope chain of the “bar” context is:

barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];
Copy the code

The identifiers of x, y, and 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
Copy the code

Scope characteristic

closure

In ECMAScript, closures are directly related to the function’s [[scope]]. As we mentioned, [[scope]] is stored when the function is created, co-existing with the function. In effect, 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 are searched in the lexical chain of the variable object (statically stored at creation time).

var x = 10;
 
function foo() {
  alert(x);
}
 
(function () {
  var x = 20;
  foo(); // 10, but not 20
})();
Copy the code

Again, we see that during identifier resolution, the lexical scope defined when the function was created is used — the variable resolves to 10 instead of 30. Furthermore, the example makes it clear 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.

[[scope]] of a function created by a constructor

In the above example, we saw that the function’s [[scope]] property is obtained at function creation time and is used to access all the variables of the parent context. There is one important exception to this rule, however, and it relates to functions created through function constructors.

var x = 10;
 
function foo() {
 
  var y = 20;
 
  function barFD() {// declare alert(x); alert(y); } var barFE =function() {alert(x); alert(y); }; var barFn = Function('alert(x); alert(y); '); barFD(); // 10, 20 barFE(); // 10, 20 barFn(); / / 10,"y" is not defined
 
}
 
foo();
Copy the code

We see that Function “bar” created from Function constructor does not access variable “y”. But that doesn’t mean the function “barFn” doesn’t have the [[scope]] property (otherwise it can’t access the variable “x”). The problem is that the [[scope]] property of a function created through a function constructor is always a unique global object. With this in mind, it is not possible, for example, to create a top-level context closure that is not global.

Two dimensional scoped chain lookup

One of the most important things to look up in the scope chain is that the attributes of the variable object (if any) are taken into account — due to the stereotype feature of ECMAScript. If a property is not found directly in the object, the query continues in the stereotype chain. It’s called two-dimensional chain search. (1) Scope chain link; (2) Each scope chain — down to the prototype chain link. We can see this effect if we define attributes in Object.prototype.

function foo() { alert(x); } Object.prototype.x = 10; foo(); / / 10Copy the code

Live objects have no archetypes, as we can see in the following example:

function foo() {
 
  var x = 20;
 
  function bar() { alert(x); } bar(); } Object.prototype.x = 10; foo(); / / 20Copy the code

Scope chains in global and eval contexts

It’s not necessarily fun, but I have to give you a hint. The scope chain of a global context contains only global objects. The context of code Eval has the same chain of scopes as the current calling context.

globalContext.Scope = [
  Global
];
 
evalContext.Scope === callingContext.Scope;
Copy the code

The impact of code execution on the scope chain

In ECMAScript, there are two declarations that modify the scope chain during code execution. This is the with statement and the catch statement. They are added to the very front of the scope chain, and objects must be looked up in 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]]
Copy the code

In this example, you add the object, which is its argument (thus, without the prefix, the properties of the object become accessible).

var foo = {x: 10, y: 20};
 
with (foo) {
  alert(x); // 10
  alert(y); // 20
}
Copy the code

The scope chain is modified to look like this:

Scope = foo + AO|VO + [[Scope]]
Copy the code

Again, we see that the resolution of identifiers in objects is added to the front of the scope chain with the with statement:

var x = 10, y = 10; with ({x: 20}) { var x = 30, y = 30; alert(x); // 30 alert(y); // 30 } alert(x); // 10 alert(y); / / 30Copy the code

What happens when you enter the context? Identifiers “X” and “y” have been added to the variable object. In addition, the following changes are made during the code runtime:

  • x = 10, y = 10;
  • Object {x:20} is added to the front of the scope;
  • Inside with, a VAR declaration is encountered, but of course nothing is created because all variables have been parsed and added when the context is entered;
  • In the second step, only the variable “x” is changed. In fact, the “X” in the object is now parsed and added to the very front of the scope chain, where “x” is 20 and becomes 30;
  • There are also changes to the variable object “y”, which is parsed from 10 to 30.
  • In addition, after the with declaration is complete, its specific object is removed from the scope chain (the changed variable “x” –30 is also removed from that object), which means that the structure of the scope chain is restored to the state it was 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 declaration run

Similarly, the exception parameter of the catch statement becomes accessible, and it creates a new object with only one attribute — the exception parameter name. The graphic looks something like this:

try {
  ...
} catch (ex) {
  alert(ex);
}
Copy the code

Change the scope chain to:

var catchObject = {
  ex: <exception object>
};
 
Scope = catchObject + AO|VO + [[Scope]]
Copy the code

conclusion

At this stage, we considered almost all of the commonly used concepts related to the execution context, as well as the details associated with them. Follow the plan – Detailed analysis of function objects: function types (function declarations, function expressions) and closures. By the way, it is not easy to summarize after work just to share with everyone. If there is any similarity or pure plagiarism, Hoho welcome everyone to discuss the technology together. If feel helpful to you please give xiaobian point under caution.

I hope this series will help you understand JavaScript in depth