Execution context and execution context stack

Variable promotion and function promotion

Variable ascension

The variable declared by the var keyword, which can be accessed before the statement is defined, has the value undefined

console.log(a)   // undefined
var a = 'Fitz'

// The actual process of the above code is:
var a
console.log(a)
a = 'Fitz'
Copy the code

Function increase

A function declared with the function keyword can be successfully executed completely before the function is defined

// called before the function declaration
sayName('Fitz') // 'Fitz'
function sayName (myName) {
    console.log(myName)
}

// The actual process of the above code is:
function sayName (myName) {
    console.log(myName)
}
sayName('Fitz') // 'Fitz'
Copy the code

Note: a function promotion must be declared by the function keyword, not by a direct variable declaration, because this declaration is actually a normal variable declaration, which is consistent with the case of variable promotion

func()  Func is not a function
var func = function () {
    console.log('hello')}Copy the code

Priority of variable promotion and function promotion

After introducing variable promotion and function promotion, one might ask: If my variable name and function name are the same, which one will be overridden? (Who will be the final object)

On this question of exploring the priority of the two, I will directly say the conclusion: perform function promotion first, then perform variable promotion

With this conclusion in mind, here are two questions that scream WTF from the heart

Let’s start with topic 1:

var a = 123
function a () {
    console.log('a()')}console.log(a)  // Guess what the result is
a()             // Guess what the result is
Copy the code

Correct answers to the questions above

var a = 123
function a() {
    console.log('a()')}console.log(a)  / / 123
a()             // error: a is not a function
Copy the code

According to the conclusion: should function promotion be performed first, then variable promotion be performed

According to the results, this conclusion is totally wrong. Hold on

The complete conclusion about variable promotion and function promotion is as follows:

  • Function promotion is performed before variable promotion
  • Variable declarations do not override function declarations and are ignored if they have the same name
  • Variable assignments override function declarations (assignments)

So according to the full conclusion, the actual flow of the problem code is like this:

function a() {  // The function is promoted
    console.log('a()')}var a       // Variable promotion does not override the above function
a = 123     // Function A is reassigned to a Number (123), because assignment overrides function declarations (123).
console.log(a)  / / 123
a()             // error: a is not a function
Copy the code

Feeling good? Let’s have a look at title 2:

var a
function a() {}console.log(typeof a)   // Guess what the result is
Copy the code

Answers to the questions above

var a
function a() {}console.log(typeof a)   // 'function'
Copy the code

Is not the heart of thousands of grass mud horse pentium, in any case follow the conclusion we analyze what happened, according to the conclusion of the topic code execution process should be like this:

function a() {}// The function is promoted first
var a   // The compiler ignores the variable declaration because of the same name
console.log(typeof a)   // 'function'
Copy the code

So remember the complete conclusion is the most important, otherwise encounter similar to these two questions, although know there is a pit, but the probability is still directly into the jump

But it’s pretty gross to remember this long conclusion, so instead of remembering: perform function promotion first, then variable promotion, etc., just remember: == Perform variable promotion first, then function promotion ==, and you’ll find that everything above works except one sentence

Finally, I will give you another topic to consolidate:

var c = 1
function c(c){
    console.log(c)
}
c(2)    // What is the result
Copy the code

Follow conclusion walk, these everybody total can’t make a mistake, still wrong of consciousness hair comment at the back of article!

var c = 1
function c(c){
    console.log(c)
}
c(2)    // error: c is not a function

// The actual execution of the above code
function c(c){
    console.log(c)
}
var c  // Remember: variable declarations with the same name are ignored
c = 1

c(2)    // c is a function of type 1.
Copy the code

A topic on variable promotion introduces the respective characteristics of the var keyword and the let keyword

Without further ado, let’s start with the title

if(! (bin window)) {var b = 1
}
console.log(b)  // What is the result
Copy the code

The answer is output undefined

I venture to guess that someone’s answer is: false

The person reporting the error should be thinking the same thing I was thinking at first, but ignoring the var keyword, which has no block-level scope ==

What is block-level scope, as opposed to function scope and global scope, which is the big scope inside the braces of for loops and if statements

Since var has no block-level scope, the curly braces of if do not bind it, so the problem code runs like this

var b
if(! (bin window)) {// window already has a b variable with undefined
    b = 1
}
console.log(b)  // undefined
Copy the code

The lack of block-level scope for the var keyword raises another classic problem: indexing for loops

for (var i=0; i<10; i++){
    setTimeout(
        () = >{
            console.log(i)  // Guess the result
        },
        2000)}Copy the code

The answer is: after about 2s it directly prints 10 10s

The timer is a hole in the JS system, which can be filled later. For now, we will focus on the index of the for loop

This is because the var keyword has no block-level scope. When the timer is executed asynchronously, the synchronous execution of for has already finished, and I has already become 10. Therefore, the output of all 10 timers is 10

So what’s the solution? Here I introduce another big hole —- closure, which we can solve by using the properties of closures

for (var i = 0; i < 10; i++) {
    (function (outerIAsParm) {
        setTimeout(
            () = > {
                console.log(outerIAsParm)
            },
            2000
        )
    })(i)   // The closure is generated
}
Copy the code

It’s a little bit complicated to solve this problem with closures, and the concept of closures is still very difficult for me, and I’ll give you a summary of the concept of closures later, so you can take a look

So what’s a faster way to solve this problem?

Let has its own block-level scope, which allows the I of each for loop to be read and saved by the timer inside it. There is a concept called let deadfield (this is related to the lexical environment covered in the execution context section below)

for (let i = 0; i < 10; i++) {
    setTimeout(
        () = > {
            console.log(i)
    },
    2000
}
Copy the code

Execution context

== variable promotion and function promotion are caused by the execution context ==

The execution context is divided into global execution context and function (local) execution context according to where the code is written, as well as scope

Global execution context

The global execution context is the work done automatically by the JS engine before the global code execution. Its work has the following parts

== First :== specifies the window object as a global execution context object, i.e., globalContext is the Window object

console.log(window)             // window
Copy the code

== Then :== preprocesses the data in the global scope

  • Collect all variables defined in the global scope using the var keyword, assign the value to undefined, and add it as an attribute of the global execution context object window

  • Collect all functions declared with the function keyword in the global scope and add methods to the global execution context object Window

  • Point the context object to the global execution context object, that is: this = window

== Finally :== begins execution of global code, normal assignment of variable assignments, execution of statements, expressions, etc

So based on the concepts above, the code execution for this example should look like this

console.log(a)  // undefined
test()          // 'Fitz'

var a = 123
function test () {
    console.log('Fitz')}// The actual code execution should look like this
window.test = function () {
    console.log('Fitz')}var a
console.log(a)  // undefined
test()          // 'Fitz'
a = 123
Copy the code

Function (local) execution context

Before a function is called, that is, ready to execute the code in the function body, an execution context object for the function is created, which automatically does the following

== First :== preprocesses variables and parameters inside the function

  • The parameter is assigned to the value of the argument and then added as a property of the function execution context object

    function test (parm){}
    test(1)
    // parm = 1 in the context of function execution
    Copy the code
  • The Arguments object is assigned a pseudo-array of arguments, which are then added as properties of the function execution context object

    function test (parm){
        console.log(arguments)
    }
    test(1.2.3)
    Copy the code

  • The variables declared by the var keyword and the functions declared by the function keyword are similar to the global execution context, but are confined to the function scope

    function test () {
        console.log(a)  // undefined
        innerFunc()     // 'hello'
    
        function innerFunc() {
            console.log('hello')}var a = 'haha'
    }
    
    test()
    Copy the code

== The context object changes dynamically according to the object on which the function is called

== Finally :== starts executing the function body code

So based on the concepts above, the code execution for this example should look like this

function func(a1) {
    console.log(a1) / / 666
    console.log(a2) // undefined
    a3()            // a3()
    console.log(this)   // window
    console.log(arguments)  // The pseudo-array has elements 666 888
    var a2 = 3
    function a3() {
        console.log('a3()')
    }
}
func(Awesome!.888)

// The actual process of the above code is:
function func(a1) {
    The /* 'var' keyword declares variables and the 'function' keyword declares functions, similar to the global execution context, but restricted to function scope */
    function a3() {
       console.log('a3()')}var a2
    console.log(a1) // The parameter is assigned a1 = 666
    console.log(a2) // undefined
    a3()            // a3()

    console.log(this)   // The context object changes dynamically according to the object on which the function is called. This is the default binding case
    console.log(arguments)  The // arguments object is assigned a pseudo-array of arguments
    a2 = 3
}
func(Awesome!.888)  // Call the function. Before executing a statement within a function, the function execution context operations described above are automatically performed
Copy the code

Details the process of executing a context

We have already described what happens automatically when a complete execution context and a function execution context are created, but we will also take a closer look at what happens when an execution context object is created (i.e., the steps described above and what the process is like here)

First of all, this part of the knowledge comes from the blogger: Listen to the wind is the wind blogger’s “an article to understand JS execution context”.

The creation of an execution context object is divided into two phases: the creation phase and the execution phase

The execution phase is the execution of the global code or the execution of a function call. Then focus on the creation phase

Create a stage

The creation phase is responsible for three things:

  • To determine this
  • Create LexicalEnvironment components
  • Creating VariableEnvironment components (VariableEnvironment)

Corresponding to the steps described above (first… And then… And finally…).

Refer to the pseudo-code in the reference blog to show the creation process:

ThisBinding = <this value>, // create LexicalEnvironment component = {}, // create a VariableEnvironment component VariableEnvironment = {},}Copy the code

As this has been introduced in other notes, we need to focus on the lexical environment component and the variable environment component

The lexical environment component is composed of two parts: the environment record and the introduction record to the external environment. It is a mapping structure about the identifier (variable name/function name) and the value of the actual object (common data/address value)

var a = 123
var b = {}
Identifier A maps the value of type Number 123 Identifier B maps the address of the heap memory */
Copy the code

The environment record is used to store the actual location of variables and function declarations in the current environment, which is used to mark the actual assignment location

console.log(a)
var a = 123

// The actual execution of the code is
var a
console.log(a)
a = 123
/* The environment record is used to indicate that a is actually assigned after console.log(). Note: this is a personal guess, and the truth is not certain */
Copy the code

The external environment introduces records to store which external environments are accessible to the current environment. This is similar to what variables/values are accessible under the scope chain. The lexical environment is divided into global and function execution contexts

The global lexical environment is shown in pseudocode

// Global environment
GlobalExectionContext = {
    // Global lexical environment
    LexicalEnvironment: {
        // Environment record
        EnvironmentRecord: {
            Type: "Object".// The type is the object environment record
            // The identifier is bound here
        },
        // The external reference record is null
        outer: [null]}}Copy the code

The functional lexical environment is shown in pseudocode:

// Function environment
FunctionExectionContext = {
    // function lexical environment
    LexicalEnvironment: {
        // Environmental record
        EnvironmentRecord: {
            Type: "Declarative".// The type is declarative environment record
            // The identifier is bound here
            arguments: {0: 'fitz'.length: 1}},// References to external records may be global objects
        // It can also be the environment of another function, such as a nested function that introduces records to the external environment
        outer: < Global or outerfunction environment reference >
    }
}
Copy the code

The variable environment component is a special lexical environment that only stores variables declared by var

So in conclusion

var a = 123
let b = 'Fitz'
const c = 'Lx'
var d
function e (parm1, parm2) {}
d = e('p1'.'p2')
Copy the code

The execution context object creation process for the above instance is completely represented in pseudocode:

// Global execution context
GlobalExectionContext = {
    // This is bound to a global object
    ThisBinding: <Global Object>, // <Global Object> => window // LexicalEnvironment: { < initialized >, c: < uninitialized >, e: < initialized > < function >}, outer: <null> // the environment component VariableEnvironment: {EnvironmentRecord: {Type: D: undefined}, d: undefined}, d: undefined}, d: undefined < null >}} / / = = = = = = = = = = = = = = = = = = = = = = = = = = = line = = = = = = = = = = = = = = = = = = = = = = / / function execution context FunctionExectionContext = { This => window ThisBinding: <Global Object>, // LexicalEnvironment: {EnvironmentRecord: Arguments: {parm1: 'p1', parM1: 'p2', length: </Global> outer: <GlobalEnvironment>}, VariableEnvironment: {EnvironmentRecord: {Type: </Global> outer: <GlobalEnvironment>}}Copy the code

Execution context stack

When I introduced the execution context above, both global and function (local) execution contexts, I mentioned the concept of an execution context object. It is now around this execution context object that I introduce what an execution context stack is.

First, the life cycles of global and local execution context objects are different

  • The global execution context object is generated before global code execution. And it stays there until the browser closes
  • The function execution context object is generated only when the function is called and the statement inside the function is executed. After the function call, the execution context object is destroyed. This concept is like the relationship between function scope and THE JS garbage collector

Second, the execution context object is not a real JS object, it is a virtual concept level object, the reason why it is called an object is to make it easier to understand

Here is a diagram of the Execution context stack of the English website

This topic explains the execution context object and the execution context stack

var a = 10
var bar = function (x) {
    var b = 5
    foo(x + b)
}

var foo = function (y) {
    var c = 5
    console.log(a + c + y)
}

bar(10)
Copy the code

My question to the above question is: == how many execution context objects are generated above, and how do they relate to the execution context stack ==

With questions, let’s explore and solve them

First, as I mentioned above, the global execution context object Window is available when the browser is created, whereas the function execution context object is available only when the function is called. So the problem == produces three execution context objects ==


// 1. You are now in the global execution context object

var a = 10
var bar = function (x) {
    var b = 5
    foo(x + b)  // 3. Function call, generate a function execution context
}

var foo = function (y) {
    var c = 5
    console.log(a + c + y)
}

bar(10) // 2. Function call, which generates a function execution context
Copy the code

How about calling the bar() function twice? The answer is: five execution context objects


// 1. You are now in the global execution context object


var a = 10
var bar = function (x) {
    var b = 5
    foo(x + b)  // 3. Function call, generate a function execution context
                // 5. Function call, generate a function execution context
}

var foo = function (y) {
    var c = 5
    console.log(a + c + y)
}

bar(10) // 2. Function call, which generates a function execution context
bar(10) // 4. Function call, generate a function execution context

/* Note that the function execution context object is destroyed at the end of the function call
Copy the code

From this, we can summarize the formula for the number of execution context objects: Execution context objects = number of function executions + 1

Having said that, how does this execution context object relate to the execution context stack?

Well, before global code execution, the JS engine creates a stack to store and manage all execution context objects

Note: all means both global and local are placed on the stack

Many of my notes refer to the stack data structure several times without explaining it properly. Although it is not a problem, but strive to idiocy knowledge, also no matter long wordy.

As a data structure, the stack has LIFO (last in, first out) property, so much for my humble knowledge. It’s hard to understand just saying it like that

Combined with the things in life, or relatively easy to understand

At this point, we know that the execution context stack is to better manage the execution context object, or the above title, let’s briefly visualize the execution context stack


// 1. You are now in the global execution context object

var a = 10
var bar = function (x) {
    var b = 5
    foo(x + b)  // 3. Function call, generate a function execution context
}

var foo = function (y) {
    var c = 5
    console.log(a + c + y)
}

bar(10) // 2. Function call, which generates a function execution context
Copy the code

Perform context stack interview questions and parse them

// What is the result of the execution
// How many execution contexts (objects) are generated
console.log('global begin: ' + i)
var i = 1
foo(1)
function foo(i) {
    if (i === 4) {
        return
    }
    console.log('foo() begin: ' + i)
    foo(i + 1)
    console.log('foo() end: ' + i)
}
console.log('global end: ' + i)
Copy the code

Visual parsing:

// What is the result of the execution
// How many execution contexts (objects) are generated
console.log('global begin: ' + i)   // Variable promotion output undefined
var i = 1

// This part focuses on the visual analysis of the picture
foo(1)
function foo(i) {
    if (i === 4) {
        return
    }
    console.log('foo() begin: ' + i)
    foo(i + 1)      // recursive call
    console.log('foo() end: ' + i)
}


console.log('global end: ' + i) //1, the global scope of I has not changed, do not be affected by the function scope I
Copy the code