Home >Web Front-end >JS Tutorial >Detailed explanation of execution context and execution stack in JS

Detailed explanation of execution context and execution stack in JS

青灯夜游
青灯夜游forward
2020-11-06 17:47:031976browse

Detailed explanation of execution context and execution stack in JS

If you are a JavaScript developer, or want to become a JavaScript developer, then you must know the internal execution mechanism of a JavaScript program. 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 mechanism, scope and closure. This article introduces these concepts in as easy-to-understand a way as possible.

Recommended tutorial: "JavaScript Video Tutorial"

1. Execution Context (Execution Context)

1. What is execution context

In short, 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. Type of execution context

There are three types of execution context:

  • Global execution context: This is the default and most basic execution context. Code that is not in any function is in the global execution context. It does two things: 1. Create a global object. In the browser, this global object is the window object. 2. Point the this pointer to this global object. Only one global execution context can exist in a program.
  • Function execution context: Each time a function is called, a new execution context is created for the function. Each function has its own execution context, but it is only created when the function is called. Any number of function execution contexts can exist in a program. Whenever a new execution context is created, it performs a series of steps in a specific order, which will be discussed later in this article.
  • Eval function execution context: The code running in the eval function also gets its own execution context, but since the eval function is not commonly used by Javascript developers, it will not be discussed here.

2. The life cycle of execution context

The life cycle of execution context includes three stages: Creation stage→Execution stage→Recycling stage, this article focuses on Creation phase.

1. Creation phase

When the function is called, but before any of its internal code is executed, the following three things will be done:

  • Create a variable object: First initialize the parameters of the function and promote the function declaration and variable declaration. This will be explained in detail below.
  • Create scope chain (Scope Chain): During the creation phase of the execution context, the scope chain is created after the variable object. The scope chain itself contains variable objects. Scope chain is used to resolve variables. When asked to resolve a variable, JavaScript always starts at the innermost level of code nesting. If the variable is not found at the innermost level, it jumps to the parent scope one level up until it finds the variable.
  • Determine where this points: including many situations, which will be explained in detail below

Before a JS script is executed, the code must be parsed (so JS is a scripting language that interprets and executes) , when parsing, a global execution context will be created first, and all variable and function declarations in the code that will be executed will be taken out first. Variables are temporarily assigned to undefined, and functions are declared and ready for use. After this step is completed, the formal execution process can then begin.

In addition, before a function is executed, a function execution context will also be created, which is similar to the global context, but the function execution context will have this arguments and function parameters.

2. Execution phase

Execution variable assignment, code execution

3. Recycling phase

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

3. Details of variable promotion and this pointing

1. Variable declaration promotion

Most programming languages ​​declare variables first and then use them, but in JS, things are a little different. Same:

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

The above code normally outputs undefined instead of reporting an error Uncaught ReferenceError: a is not defined, this is because of statement hoisting, which is equivalent to the following code :

var a; //声明 默认值是undefined “准备工作”
console.log(a);
a = 10; //赋值

2. Function declaration promotion

We all know that there are two ways to create a function, one is through function declarationfunction foo(){}
The other is through function expressionvar foo = function(){}, so what is the difference between the two in function promotion?

console.log(f1); // function f1(){}
function f1() {} // 函数声明
console.log(f2); // undefined
var f2 = function() {}; // 函数表达式

Next we use an example to illustrate this problem:

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, an error is reported when foo() is called, but bar can be called normally.

We said before that variables and functions will rise. When encountering the function expression var foo = function(){}, var foo will first rise to At the top of the function body, however, the value of foo at this time is undefined, so an error is reported when foo() is executed.

而对于函数bar(), 则是提升了整个函数,所以bar()才能够顺利执行。

有个细节必须注意:当遇到函数和变量同名且都会被提升的情况,函数声明优先级比较高,因此变量声明会被函数声明所覆盖,但是可以重新赋值。

alert(a); //输出:function a(){ alert('我是函数') }
function a() {
    alert("我是函数");
} //
var a = "我是变量";
alert(a); //输出:'我是变量'

function 声明的优先级比 var 声明高,也就意味着当两个同名变量同时被 function 和 var 声明时,function 声明会覆盖 var 声明

这代码等效于:

function a() {
    alert("我是函数");
}
var a; //hoisting
alert(a); //输出:function a(){ alert('我是函数') }
a = "我是变量"; //赋值
alert(a); //输出:'我是变量'

最后我们看个复杂点的例子:

function test(arg) {
    // 1. 形参 arg 是 "hi"
    // 2. 因为函数声明比变量声明优先级高,所以此时 arg 是 function
    console.log(arg);
    var arg = "hello"; // 3.var arg 变量声明被忽略, arg = 'hello'被执行
    function arg() {
        console.log("hello world");
    }
    console.log(arg);
}
test("hi");
/* 输出:
function arg(){
    console.log('hello world') 
    }
hello 
*/

这是因为当函数执行的时候,首先会形成一个新的私有的作用域,然后依次按照如下的步骤执行:

  • 如果有形参,先给形参赋值
  • 进行私有作用域中的预解释,函数声明优先级比变量声明高,最后后者会被前者所覆盖,但是可以重新赋值
  • 私有作用域中的代码从上到下执行

3. 确定 this 的指向

先搞明白一个很重要的概念 —— this 的值是在执行的时候才能确认,定义的时候不能确认! 为什么呢 —— 因为 this 是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候。看如下例子:

// 情况1
function foo() {
  console.log(this.a) //1
}
var a = 1
foo()

// 情况2
function fn(){
  console.log(this);
}
var obj={fn:fn};
obj.fn(); //this->obj

// 情况3
function CreateJsPerson(name,age){
//this是当前类的一个实例p1
this.name=name; //=>p1.name=name
this.age=age; //=>p1.age=age
}
var p1=new CreateJsPerson("尹华芝",48);

// 情况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

// 情况5
<button id="btn1">箭头函数this</button>
<script type="text/javascript">
    let btn1 = document.getElementById(&#39;btn1&#39;);
    let obj = {
        name: &#39;kobe&#39;,
        age: 39,
        getName: function () {
            btn1.onclick = () => {
                console.log(this);//obj
            };
        }
    };
    obj.getName();
</script>

接下来我们逐一解释上面几种情况

  • 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window
  • 对于 obj.foo() 来说,我们只需要记住,谁调用了函数,谁就是 this,所以在这个场景下 foo 函数中的 this 就是 obj 对象
  • 在构造函数模式中,类中(函数体中)出现的 this.xxx=xxx 中的 this 是当前类的一个实例
  • call、apply 和 bind:this 是第一个参数
  • 箭头函数 this 指向:箭头函数没有自己的 this,看其外层的是否有函数,如果有,外层函数的 this 就是内部箭头函数的 this,如果没有,则 this 是 window。

Detailed explanation of execution context and execution stack in JS

四、执行上下文栈(Execution Context Stack)

函数多了,就有多个函数执行上下文,每次调用函数创建一个新的执行上下文,那如何管理创建的那么多执行上下文呢?

JavaScript 引擎创建了执行上下文栈来管理执行上下文。可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则

Detailed explanation of execution context and execution stack in JS

从上面的流程图,我们需要记住几个关键点:

  • JavaScript 执行在单线程上,所有的代码都是排队执行。
  • 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
  • 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。
  • 浏览器的 JS 执行引擎总是访问栈顶的执行上下文。
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈。

我们再来看个例子:

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

上述代码运行按照如下步骤:

  • 当上述代码在浏览器中加载时,JavaScript 引擎会创建一个全局执行上下文并且将它推入当前的执行栈
  • 调用 changeColor 函数时,此时 changeColor 函数内部代码还未执行,js 执行引擎立即创建一个 changeColor 的执行上下文(简称 EC),然后把这执行上下文压入到执行栈(简称 ECStack)中。
  • 执行 changeColor 函数过程中,调用 swapColors 函数,同样地,swapColors 函数执行之前也创建了一个 swapColors 的执行上下文,并压入到执行栈中。
  • swapColors 函数执行完成,swapColors 函数的执行上下文出栈,并且被销毁。
  • changeColor 函数执行完成,changeColor 函数的执行上下文出栈,并且被销毁。

Detailed explanation of execution context and execution stack in JS

更多编程相关知识,请访问:编程学习网站!!

The above is the detailed content of Detailed explanation of execution context and execution stack in JS. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:cnblogs.com. If there is any infringement, please contact admin@php.cn delete