MEMEPh. ideas that are worth sharing...

Deep understanding of JavaScript execution context and execution stack

Foreword


If you are a JavaScript developer, or want to be a JavaScript developer, then you must know the internal execution mechanism of JavaScript programs. Execution context and execution stack are one of the key concepts in JavaScript and one of the difficulties in JavaScript. Understanding execution context and execution stack also helps to understand other JavaScript concepts such as hoisting, scope, and closures. This article tries to introduce these concepts in a way that is as easy to understand as possible.

 

1. Execution Context


1. What is an execution context

In short, the execution context is the abstract concept of the environment in which the current JavaScript code is parsed and executed. Any code running in JavaScript is run in the execution context.

 

2. The type of execution context

There are three types of execution contexts in total:

 

Second, the life cycle of the execution context


The life cycle of the execution context includes three stages: creation stage → execution stage → recycling stage , and this article focuses on the creation stage.

1. Creation stage

When a function is called, but before executing any of its internal code, it does three things:

Before a JS script is executed, the code must be parsed (so JS is a scripting language for interpretation and execution). When parsing, a global execution context will be created first, and the variables and function declarations to be executed in the code will be taken out first. Variables are temporarily assigned to undefined, and functions are declared ready for use. This step is done, and then the formal execution process begins.

In addition, before a function is executed, it will also create a function execution context, which is similar to the global context, but there will be more this arguments and function parameters in the function execution context.

 

2. Execution stage

Perform variable assignment, code execution

 

3. Recycling stage

The execution context is popped from the stack and waiting for the virtual machine to recycle the execution context

 

3. Variable promotion and details of this pointing


1. Variable declaration promotion

Most programming languages ​​declare variables before using them, but in JS, things are a little different:

console.log(a)// undefined
var a = 10

The above code outputs normally undefinedinstead of reporting an error Uncaught ReferenceError: a is not definedbecause of hoisting, which is equivalent to the following code:

var a; //declare the default value is undefined "ready to work"
console.log(a);
a=10; //assignment

 

2. Function declaration promotion

We all know that there are two ways to create a function, one is through function declaration function foo(){}

and the other is through function expression var foo = function(){}. What is the difference between these two in function promotion?

console.log(f1) // function f1(){}
function f1() {} // function declaration
console.log(f2) // undefined
var f2 = function() {} // function expression

Next, we illustrate this problem with an example:


function test() {
    foo(); // Uncaught TypeError "foo is not a function"
    bar(); // "this will run!"
    var foo = function () { // function expression assigned to local variable 'foo'
        alert("this won't run!");
    }
    function bar() { // function declaration, given the name 'bar'
        alert("this will run!");
    }
}
test();

In the above example, foo() is called with an error, but bar can be called normally.

We said earlier that both variables and functions will rise. When a function expression var foo = function(){} is encountered , it will first var foorise to the top of the function body. However, the value of foo is undefined at this time, so the execution reports an foo() error.

For functions bar(), the entire function is promoted so bar() that it can be executed smoothly.

There is one detail that must be noted: when a function and a variable have the same name and both are promoted, the function declaration has a higher priority, so the variable declaration will be overwritten by the function declaration, but can be reassigned.

alert(a);//Output: function a(){ alert('I am a function') }
function a(){ alert('I am a function') }//
var a = 'I am a variable';
alert(a); //output: 'I am a variable'

The function declaration has higher precedence than the var declaration, which means that when two variables with the same name are declared by function and var at the same time, the function declaration will override the var declaration

 

This code is equivalent to:

function a(){alert('I am a function')}
var a; //hoisting
alert(a); //Output: function a(){ alert('I am a function') }
a = 'I am a variable';//Assignment
alert(a); //output: 'I am a variable'

Finally, let's look at a more complex example:

function test(arg){
// 1. The formal parameter arg is "hi"
// 2. Because function declarations have higher priority than variable declarations, arg is function at this time
console.log(arg);
var arg = 'hello'; // 3. var arg variable declaration is ignored, arg = 'hello' is executed
function arg(){
console.log('hello world')
} 
console.log(arg);
}

test('hi');

/* output:

function arg(){
console.log('hello world')
} 
hello

*/

This is because when the function is executed, a new private scope is first formed, and then the following steps are followed:

 

3. Determine the point of this

First understand a very important concept - the value of this can only be confirmed when it is executed, and it cannot be confirmed when it is defined! Why - because this is part of the execution context, and the execution context needs to be determined before the code is executed, not when it is defined. See the following example:

// case 1
function foo() {
console.log(this.a) //1
}

var a = 1
foo()

// case 2
function fn(){
console.log(this);
}

var obj={fn:fn};
obj.fn(); //this->obj

// case 3
function CreateJsPerson(name,age){
//this is an instance of the current class p1
this.name=name; //=>p1.name=name
this.age=age; //=>p1.age=age
}
var p1=new CreateJsPerson("Yin Huazhi",48);

// case 4
function add(c, d){
return this.a + this.b + c + d;
}

var o = {a:1, b:3};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34


// case 5
<button id="btn1">arrow function this</button>

<script type="text/javascript">
let btn1 = document.getElementById('btn1');
let obj = {
name: 'kobe',
age: 39,
getName: function () {
btn1.onclick = () => {
console.log(this);//obj
}
};

} 

};

obj.getName();

</script>

Next, we explain the above situations one by one

 

Fourth, the execution context stack (Execution Context Stack)


There are many functions, and there are multiple function execution contexts. Each time a function is called, a new execution context is created. How to manage so many execution contexts created?

The JavaScript engine creates an execution context stack to manage execution contexts. The execution context stack can be regarded as a stack structure that stores function calls, following the principle of first-in, last-out .

From the above flowchart, we need to remember a few key points:

Let's look at another example:

var color = 'blue';
function changeColor() {
    var anotherColor = 'red';
    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }
    swapColors();
}
changeColor();

The above code runs as follows: