preface

Let’s look at some concepts before we start this article

  • Execution Context Stack
  • GO Global Context
  • Activation Objects
  • Variable Object
  • GC Global Execution Context
  • EC Execution Context
  • Callee Stack
  • ESC Execution Context Stack
  • Garbage Collection
  • LexicalEnvironment
  • VariableEnvironment
  • Environment Record

Lexical scope and dynamic scope

Scope is the area of a program’s source code that defines variables. Scope dictates how variables are looked up, that is, determining the access permissions of the currently executing code to the variables. JavaScript uses lexical scoping, also known as static scoping.

Static and dynamic scopes

Because JavaScript uses lexical scope, the scope of a function is determined at function definition time. The opposite of lexical scope is dynamic scope, where the scope of a function is determined at the time the function is called. Let’s take a closer look at an example to see the difference:

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar();

// The result is...?
Copy the code

If JavaScript is static scoped, let’s examine the execution process: Foo is executed, and first looks inside foo to see if there is a local variable value. If there is no local variable value, it looks for the code level above foo based on where it was written, so value equals 1, so the result prints 1.

Assuming that JavaScript is dynamically scoped, let’s examine the execution process: foo is executed, and still looks inside foo for a local variable value. If not, the value variable is looked up from within the scope of the calling function, that is, the bar function, so the result prints 2.

As we said earlier, JavaScript is static scoped, so the result for this example is 1.

You might be wondering what languages are dynamically scoped? Bash is a dynamic scope. If you don’t believe me, save the following script as scope.bash, then go to the corresponding directory, run bash./scope.bash on the command line, and see what the printed value is.

value=1
function foo () {
    echo $value;
}
function bar () {
    local value=2;
    foo;
}
bar()

// The result is 2
Copy the code

Finally, let’s look at an example from the Definitive JavaScript Guide:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();
Copy the code
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
Copy the code

Guess what the result of each piece of code is?

Here is a direct result, and both codes will print: local scope. The reason is simple, too, because JavaScript is lexical, and functions are scoped based on where they are created. The answer, to quote the definitive guide to JavaScript, is:

JavaScript functions are executed using the scope chain, which is created when the function is defined. The nested function f() is defined in the scope chain, where the variable scope must be a local variable. This binding remains in effect when f() is executed, no matter where or when f() is executed.

But what I really want you to think about here is: even though two pieces of code execute the same, what are the differences between them?

Execution Context Stack (ECStack)

What types of JavaScript executable code are there? There are three types of global code, function code, and eval code.

For example, when a function is executed, there is preparation, which is, to use a more technical term, an “execution context.”

Execution context

The question then arises, how do we manage the creation of so many execution contexts when we write so many functions? So the JavaScript engine creates the Execution Context stack (ECS) to manage the Execution context. To simulate the Execution context stack’s behavior, let’s define the Execution context stack as an array:

ECStack = [];
Copy the code

When JavaScript starts to interpret execution code, the first thing it encounters is global code, so initialization pushes a global execution context onto the execution context stack, which we call a globalContext, and only when the entire application ends, The ECStack is cleared, so there is always a globalContext at the bottom of the ECStack before the program ends:

ECStack = [
    globalContext
];
Copy the code

JavaScript now encounters the following code:

function fun3() {
    console.log('fun3')}function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();
Copy the code

When a function is executed, an execution context is created and pushed onto the execution context stack. When the function is finished, the execution context of the function is ejected from the stack. With that in mind, let’s see what we can do with the code above:

/ / pseudo code

// fun1()
ECStack.push(<fun1> functionContext);

// create a context for the execution of fun2
ECStack.push(<fun2> functionContext);

// fun2 also calls fun3!
ECStack.push(<fun3> functionContext);

// fun3 is executed
ECStack.pop();

// fun2 completes execution
ECStack.pop();

// fun1 completes execution
ECStack.pop();

// Javascript then executes the following code, but there is always a globalContext at the bottom of the ECStack
Copy the code

Now that we know how the execution context stack handles execution context, let’s take a look at the example from the JavaScript Guru’s Guide above to simulate the first code:

ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
Copy the code

Let’s simulate the second code:

ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
Copy the code

Variable objects (GO, VO, AO)

For each execution context, there are three important properties:

  • Variable Object (VO)
  • Scope chain
  • this

The VO variable object is a special object that is specific to the execution context and stores the function declarations, function parameters, and variables of the context. Since variable objects vary slightly from execution context to execution context, let’s talk about variable objects in global context and variable objects in function context.

Global context – The global variable GO

Let’s start with a concept called the global object GO. Global objects are predefined objects that act as placeholders for JavaScript global functions and global properties. By using global objects, you can access all the other predefined objects, functions, and properties.

In top-level JavaScript code, you can reference the global object with the keyword this. Because the global object is the head of the scope chain, it is always at the bottom of the execution context stack and only goes off the stack when the program destroys it. This means that all non-qualified variable and function names are queried as properties of the object.

1. With this reference, in client-side JavaScript, the global object is the Window object.

console.log(this);
Copy the code

2. A global Object is an Object instantiated by the Object constructor.

console.log(this instanceof Object);
Copy the code

3. Predefined a bunch of, well, a bunch of functions and properties.

Console.log (math.random ()); console.log(this.Math.random());Copy the code

4. Host global variables.

var a = 1;
console.log(this.a);
Copy the code

5. In client-side JavaScript, the global object has the window attribute pointing to itself.

var a = 1;
console.log(window.a);

this.window.b = 2;
console.log(this.b);
Copy the code

Implementation process

The life cycle of a context consists of three phases: create phase -> execute phase -> reclaim phase. We can also call:

  1. Enter execution context
  2. Code execution
  3. Recovery phase (GC)

Enter execution context

When you enter the execution context, where no code has been executed, the variable object will include:

  1. All arguments to a function (if function context)
    • The property of a variable object consisting of a name and corresponding value is created
    • There are no arguments and the property value is set to undefined
  2. Function declaration
    • The properties of a variable object consisting of a name and corresponding values (function-object) are created
    • If the variable object already has an attribute of the same name, replace the attribute completely
  3. Variable declarations
    • A variable object property consisting of a name and a corresponding value (undefined) is created;
    • If the variable name is the same as the formal parameter or function already declared, the variable declaration does not interfere with such attributes that already exist

Here’s an example:

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;

}

foo(1);
Copy the code

After entering the execution context, the AO is:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}
Copy the code

Code execution

During the code execution phase, the code will be executed sequentially and the value of the variable object will be modified according to the code. When the code is executed, the AO at this time is:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}
Copy the code

Let’s summarize the process of creating variable objects

  1. The variable object initialization of the global context is the global object
  2. Function context variable object initializations include only Arguments objects
  3. Initial attribute values such as parameters, function declarations, and variable declarations are added to variable objects when entering the execution context
  4. During code execution, the attribute values of the variable object are modified again

The scope chain

In the above scope and static, dynamic scope when looking for the variable will be from the current context variable object lookup, if not found, will be from the parent class (the parent of the lexical level) of the execution context variable object lookup, always find global context variable object, is the global object. Thus a linked list of variable objects in multiple execution contexts is called a scoped chain.

Function creates

The scope of a function is determined when the function is defined. This is because the function has an internal property [[scope]] into which all the parent objects are stored when the function is created. You can understand that [[scope]] is a hierarchy of all parent objects, but note: [[scope]] does not represent a complete scope chain!

Here’s an example:

function foo() { function bar() { ... }}Copy the code

When a function is created, its respective [[scope]] is:

foo.[[scope]] = [
  globalContext.VO
];

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

When a function is called, the active object is added to the front of the action chain after the VO/AO is created by entering the function context. The Scope chain of the execution context is called Scope:

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

With the execution context stack and variable object above, let’s summarize the process of creating a scope chain using the following example

var scope = "global scope";
function checkscope(){
    var scope2 = 'local scope';
    return scope2;
}
checkscope();
Copy the code

1. The checkScope function is created to save the scope chain to the inner property [[scope]]

checkscope.[[scope]] = [
    globalContext.VO
];
Copy the code

2. Execute the checkScope function, create the checkScope function execution context, checkScope function execution context is pushed into the execution context stack

ECStack = [
    checkscopeContext,
    globalContext
];
Copy the code

Create a scope chain by copying the [[scope]] property of the function

checkscopeContext = {
    Scope: checkscope.[[scope]],
}
Copy the code

Step 2: Use arguments to create the active object, and then initialize the active object by adding parameters, function declarations, and variable declarations

CheckscopeContext = {AO: {arguments: {length: 0}, scope2: undefined}, Scope: checkScope.[[Scope]],}Copy the code

5. Step 3: Press the active object AO at the top of the CheckScope chain

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: undefined
    },
    Scope: [AO, [[Scope]]]
}
Copy the code

6. Execute the function. Modify the AO attribute values as the function is executed

checkscopeContext = {
    AO: {
        arguments: {
            length: 0
        },
        scope2: 'local scope'
    },
    Scope: [AO, [[Scope]]]
}
Copy the code

7. The value of scope2 is found, and the function context is ejected from the execution context stack

ECStack = [
    globalContext
];
Copy the code

This principle

A picture takes you through this problem

Closure principle

In ECMAScript, closures refer to:

  1. From a theoretical point of view: all functions. Because they both store the data of the upper context at the time of creation. This is true even for simple global variables, since accessing a global variable in a function is equivalent to accessing a free variable, using the outermost scope.
  2. As a practical matter, the following functions are closures:
    • It persists even if the context in which it was created has been destroyed (for example, an inner function returns from a parent function)
    • Free variables are referenced in the code

Let’s start with an example of how the execution context stack and execution context change in this code.

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();
Copy the code

Above we have analyzed, and here we directly give a brief implementation process:

  1. Enter the global code, create the global execution context, and push the global execution context onto the execution context stack
  2. Global execution context initialization
  3. Execute the checkScope function, create the CheckScope function execution context, the checkScope execution context is pushed into the execution context stack
  4. Checkscope performs context initialization, creating variable objects, scope chains, this, and so on
  5. When the checkScope function completes execution, the CheckScope execution context pops out of the execution context stack
  6. Execute f function, create f function execution context, f execution context is pushed into the execution context stack
  7. F performs context initialization, creating variable objects, scope chains, this, and so on
  8. When the f function completes execution, the f function context pops out of the execution context stack

To understand this process, we should think about the question, and that is:

When f is executed, the scope of the function is destroyed, and the scope of the function is read.

When we look at the execution, we know that the f execution context maintains a chain of scopes:

fContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}
Copy the code

If f references a value in checkScopecontext. AO, the function can still read the checkscopecontext. AO value, even if the checkscopeContext is destroyed. But JavaScript still makes checkScopecontext. AO live in memory, and f functions can still find it through the scope chain of f functions. It’s because JavaScript does this that it implements the concept of closures.

So, let’s look at the practical definition of a closure again:

  1. It persists even if the context in which it was created has been destroyed (for example, an inner function returns from a parent function)
  2. Free variables are referenced in the code

Let’s look at an interview question

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0] (); data[1] (); data[2] ();Copy the code

The answer is both 3’s, and let’s analyze why:

Before data[0] is executed, the VO of the global context is:

globalContext = { VO: { data: [...] , i: 3 } }Copy the code

When data[0] is executed, the scope chain of data[0] is:

data[0]Context = {
    Scope: [AO, globalContext.VO]
}
Copy the code

The AO of data[0]Context does not have an I value, so it looks up globalContext.VO, where I is 3, so it prints 3. Data [1] is the same as data[2]. So let’s switch to closures:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}

data[0]();
data[1]();
data[2]();
Copy the code

Before data[0] is executed, the VO of the global context is:

globalContext = { VO: { data: [...] , i: 3 } }Copy the code

It’s exactly the same as before. When data[0] is executed, the scope chain of the data[0] function changes:

Data [0]Context = {Scope: [AO, anonymous function context.ao globalContext.vo]}Copy the code

The AO for the anonymous function execution context is:

Context = {AO: {arguments: {0: 0, length: 1}, I: 0}}Copy the code

The AO of data[0]Context does not have an I value, so the AO of data[0]Context does not have an I value, so the AO of data[0]Context looks up the anonymous function context. AO along the scope chain. Even globalContext.vo has a value of I (value 3), so it prints 0. Data [1] is the same as data[2].

conclusion

After reading the above knowledge points, we can easily conclude that the principle of closure is the context of the anonymous function on the Scope chain. The principle of this is dynamically bound. The current this refers to the current execution context. Scope:[AO,…, globalContext.ao]; Scope:[AO,…, globalContext.ao]; The principle of variable promotion is the creation phase of AO.

Into the ES5 +

After ES5+, the execution context becomes This Binding, LexicalEnvironment, VariableEnvironment.

// This determines the value, also known as this Binding. (That is, the this binding)
// LexicalEnvironment
// VariableEnvironment
  ExecutionContext = {
  ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }},Copy the code
// GlobalExectionContext = {// LexicalEnvironment: {// EnvironmentRecord: {Type: "Object", // global environment //... Outer: <null>, // reference to the external environment}}} // function execution context FunctionExectionContext = {LexicalEnvironment: {EnvironmentRecord: {Type: "Declarative",// function environment //... Outer: <Global or outer function environment reference>,}}}Copy the code

In order to continue to adapt to the var of the earlier JS, the new specification adds a VariableEnvironment. The variable environment is also a lexical environment whose environment logger contains statements declared by variables

In ES6, the lexical environment component differs from the variable environment component in that the former is used to store function declarations and variables (let and const) bindings, while the latter is only used to store variables (var) bindings.

reference

Github.com/mqyqingfeng…