preface

This blog needs to thank @Hu, I learned a lot from his blog Github. Here is my collation of knowledge. Talk about exactly what an execution context is.

JS code ready to work?

JS code is executed sequentially, for example

var foo = function () {

    console.log('foo1');

}

foo();  // foo1

var foo = function () {

    console.log('foo2');

}

foo(); // foo2
Copy the code

So if I change the code to something like this

function foo(){

     console.log('foo1');

}

foo() //foo2


function foo(){

    console.log('foo2')

}

foo() //foo2
Copy the code

I printed foo2 entirely, because in the first code example, the variable was promoted, so foo was promoted. The second code example is a function promotion, where the second function foo overrides the first. This topic may be many interview questions will be discussed here, but the use of these two examples, just to illustrate, JS code at runtime, JS engine will do some preparatory work.

So what kind of code does a JS engine encounter to prepare for this?

Execution context

This comes to what types of JavaScript executable code are.

There are three types of executable code: global code, function code, and eval code

Eval is no longer used in the specification, so it is out of the discussion.

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

The execution context stack of a function

Because there are so many functions in your code, how do you manage so many execution contexts? The JavaScript engine creates an Execution Context stack (ECS) to manage the Execution context.

Since it is called a stack, its data structure is a little bit clear, it is a first in last out data structure, we can use an array to simulate the call stack.

ECStack=[]
Copy the code

When global code is encountered, the execution context stack pushes a globalContext, which we use to represent

ECStack.push(globalContext)
Copy the code

The execution context stack is cleared only when the entire program is finished, so there is always a globalContext in the ECStack until the program is finished.

Now the JS engine encounters the function code

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

function fun1() {
    fun2();
}

fun1();
Copy the code

ESCStack does the following when the fun1 function is called

// Each function is executed with an execution context created and pushed into the execution context stack. Create a context // stack. Push (<fun1 context>) // find internal 'fun2' call ecstack. push(<fun2 context>) // Find internal 'fun3' call Ecstack. push(<log context>) // Find the internal log function call ecstack. push(<log context>) // there is no print fun3 // code is finished, Pop (<fun3 context>) ecstack. pop(<fun2 context>) ecstack. pop(<fun1 context>) ecstack. pop(<fun2 context>) At this point, ECStack is left with [globalContext] // to continue processing other code // globalContext will remain until the end of the programCopy the code

Little practice

Let’s write the execution context stack for the following code

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
Copy the code
// The global context is encountered
ECStack.push(globalContext) // Call stack at this point [globalContext]

// Call checkScope to enter the function checkScopeEcstack.push (<checkscope context>) // no internal function call, Return function f ecstack. pop(<checkscope context>) // pop // return f called ecstack. push(<f context>) // push ecstack. pop(<f context>) // popCopy the code

The variable object

When JS executes into a piece of executable code (global code, function code, eval), the execution context is created. The execution context has three important properties:

  • The variable object
  • this
  • The scope chain

A variable object is an execution context-specific data scope that holds variable and function declarations defined in the context.

Variable objects vary from execution context to execution context, so let’s look at global variable objects and function variable objects.

Global object

MDN:

A global object is an object that exists forever in the Global Scope. The Window object is a global object in the browser.

Any global variable or function can be accessed through the properties of the window.

In top-level JavaScript code, you can reference the global object with the keyword this.

For example,

console.log(this) //window
var a=1 // The property attached to the window
window.a / / 1
Copy the code

A variable object at the top-level scope (global context) is a global object

Active objects

In the context of functions, we refer to variable objects as activation Objects (AO)

Activation objects and variable objects are the same thing, but the variable object is specified or implemented by the engine and cannot be accessed in the JavaScript environment. Only when an execution context is entered can the variable object in that execution context be activated. Only an active variable object can have its properties accessed.

An active object is created when the function context is entered, and it is initialized via the function arguments property. Arguments property values are arguments objects.

Implementation process

The code in the execution context is processed in two stages: analysis and execution, which we can also call:

1. Enter the execution context

2. Code execution

Enter execution context

When you enter the execution context phase, the code has not yet been executed

Variable objects include

1. Arguments to a function (if function context)

  • The property of a variable object consisting of a name and corresponding value is created
  • Since there are no arguments, the attribute value is undefined

2. Function declaration

  • The property of a variable object consisting of a name and corresponding value is created
  • If a variable object has an attribute of the same name, its attribute is overridden

3. Variable declarations

  • The property of a variable object consisting of a name and a corresponding value (undefined) is created
  • If the variable name is the same as an already declared parameter or function, the variable declaration does not interfere with such attributes that already exist

For example

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

  b = 3;

}
foo(1)
Copy the code

After calling function foo and entering the function 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 is executed sequentially, modifying the value of the variable object based on the code

As in the previous example, when the code is finished executing, the AO is:

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

Here the variable object creation process is introduced, let’s briefly summarize what we said above:

  • The variable object initialization of the global context is the global object

  • Function context variable object initializations include only Arguments objects

  • Initial attribute values such as parameters, function declarations, and variable declarations are added to variable objects when entering the execution context

  • During code execution, the attribute values of the variable object are modified again

thinking

The first question

function foo() {
    console.log(a);
    a = 1;
}

foo(); // ???

function bar() {
    a = 1;
    console.log(a);
}
bar(); // ???
Copy the code

The first foo will return Uncaught ReferenceError: a is not defined

The second bar function prints: 1

The reason is that when the first function enters the execution context, there is no var declaration, so its context looks like this

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

When the function executes, the a declaration is not found, so an error is reported

When the second function enters the execution context, there is still no declaration of A, but when the code in the function executes, it can find a from the global scope, so it prints 1

The second question

console.log(foo);/ /???

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

var foo = 1;
console.log(foo);/ /???
Copy the code

The first log will print out the body of the function, not undefined, so you can see that the var declaration is promoted instead of the function declaration promotion and the second log will print out 1

The reason is that function declarations take precedence over variable declarations. If the variable name is the same as the declared parameter or function, the variable declaration does not interfere with the existing class of attributes.

When entering the execution context, the variable object looks like this

{
foo:undefined
}
Copy the code

The code executes like this, taking precedence over function declarations, where the variable object becomes:

{
foo:reference to function foo(){}
}
Copy the code

When the second log is executed, foo is assigned another value, and the variable object becomes:

{
foo:1
}
Copy the code

The third question

var foo = 1;
console.log(foo);/ /??
function foo(){
    console.log("foo");
};
Copy the code

When entering the context, the variable object becomes

{
foo:undefined
}
Copy the code

When executing code, the variable object becomes a function and then a 1

{
foo:reference to function foo(){}}// foo=1
{
foo:1
}
console.log(foo) // So the result is 1
Copy the code

The scope chain

When a variable is searched, it is first searched from the variable object in the current context. If there is no variable object in the parent scope, it is searched up. The final destination is to access the variable object in the outermost context. Thus a linked list of variable objects in multiple execution contexts is called a scoped chain.

When a piece of global code is executed, an execution context is generated that contains the global variable object

var a=123

globalContext.VO={
     a:123
}
Copy the code

Function to write

When you write a piece of function code, you create a lexical scope. This scope is the property inside the function, denoted by [[scope]], which holds the parent variable object, so [[scope]] is a hierarchy chain.

function fn(){}/* fn.[[scope]]=[ globalContext.VO ] */
Copy the code

A function call

When a function is called, it means that the function is activated, the function context is created, the active object is created, and then the active object (AO) is pushed to the front of the scope chain. We use scope to indicate the scope at this point

fnContext={
     Scope:[AO,fn.[[scope]]]
}
Copy the code

Combined with the example

Let’s examine the creation of variable objects and scopes in the context of the following code functions

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

Create a global variable VO, create a global variable VO, create a global variable VO, create a global variable VO, create a global variable VO, create an internal attribute [[scope]], and put the parent variable object in it.

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

2. When the function is called, create the function context and push it onto the execution stack

ECStack=[globalContext,checkscopeContext]
Copy the code

Step 1: Copy function [[scope]] property to create scope chain

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

Step 2: Create the active object, initialize the active object, add parameters, function declarations, variable declarations

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

Step 3: Press the live object to the top of the CheckScope chain

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

6. Start executing the function. As the function executes, modify the AO attribute values

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

Summarize all the knowledge

Let’s talk about the specific processing process with the following code:

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

Execute the global code, generate the global context, and push the execution stack

ECStack=[
     globalContext
]
Copy the code

2. Global context initialization

globalContext={
     VO=[global].this:globalContext.VO,
     Scope:[globalContext.VO]
}
Copy the code

Create the function [[scope]] property and store the global variable object in it

checkscope.[[scope]]={
     globalContext.VO
}
Copy the code

4. Call the function, create the function context, and press the stack

ECStack=[
     globalContext,
     checkscopeContext
]
Copy the code

5. The execution context is entered before the function is executed

  • The copy function [[scope]] property creates the scope chain
  • Create an active object with the Arguments property
  • Initializes a variable object, adding variable declarations, function declarations, and parameters
  • The live object is pressed into the top of the scope chain
    checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: undefined.f: reference to function f(){}},Scope: [AO, globalContext.VO],
        this: undefined
    }
Copy the code

The f function is created to generate the [[scope]] property and save the scope chain

f.[[scope]]=[
     checkscopeContext.AO,
     globalContext.VO
]
Copy the code

7, F function call, generate F function context, stack

ECStack=[
     globalContext,
     checkscopeContext,
     fContext
] 
Copy the code

8. Initialize the execution context before f function is executed

  • The copy function [[scope]] property creates the scope chain
  • Create an active object with the Arguments property
  • Initializes a variable object, adding variable declarations, function declarations, and parameters
  • The live object is pressed into the top of the scope chain
fContext = {
     AO: {
            arguments: {
                length: 0}},Scope: [fContext.AO, checkscopeContext.AO, globalContext.VO],
        this: undefined
    }
Copy the code

9, f function executes, along the scope chain to find the scope value, return scope value

10 and stack

// f function popstack
ECStack=[
     globalContext,
     checkscopeContext
]
// checkscope function stackECStack = [globalContext]...Copy the code

The second question

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

Execute global code, create global context, and push the stack

ECStack=[
     globalContext,
]
Copy the code

Initialize the global context

globalContext={
     VO: [global].Scope:[globalContext.VO]
     this:globalContext.VO
}
Copy the code

The checkScope function generates the internal [[scope]] property and stores it into the global context variable object

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

4, call the checkScope function, create the function context, press the stack

ECStack=[
     globalContext,
     checkscopeContext
]
Copy the code

5, the checkScope function has not been executed, the context initialization

  • Copy the function’s [[scope]] property to create the scope chain
  • Use Arguments to create active objects
  • Initializes the active object, adding parameters, function declarations, and variable declarations
  • Push the live object into the scope chain of the CheckScope
    checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: undefined.f: reference to function f(){}},Scope: [AO, globalContext.VO],
        this: undefined
    }
Copy the code

6, checkScope function execution stage, assignment

    checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: "local scope".f: reference to function f(){}},Scope: [AO, globalContext.VO],
        this: undefined
    }
Copy the code

7, when f is encountered, the inner [[scope]] property is generated and the parent variable object is placed

f.[[scope]]=[checkscopeContext.AO,globalContext.VO]
Copy the code

8, return function f, at this time the checkScope function is completed, popstack

ECStack=[
     globalContext
]
Copy the code

9, execute f function, create f function context, push the stack

ECStack=[
     globalContext,
     fContext
]
Copy the code

Initialize the context of f

  • Copy the function’s [[scope]] property to create the scope chain
  • Use Arguments to create active objects
  • Initializes the active object, adding parameters, function declarations, and variable declarations
  • Push the live object into the scope chain of the F function
fContext={
     AO: {arguments: {length:0}},Scope:[AO,checkscopeContext.AO,globalContext.VO]
     this:undefined
}
Copy the code

Checkscope function execution stage, assignment

12, find the scope variable, return, at this time f function completed, pop stack

ECStack=[
     globalContext,
]
Copy the code