Javascript必知必会(二):执行上下文和执行栈

我心飞翔 分类:javascript

一、什么是执行上下文?

答:简而言之,执行上下文是评估和执行Javascript代码的环境的抽象概念。每当Javascript代码在运行的时候,它都是在执行上下文的运行。

二、执行上下文的类型?

答:Javascript中所有的程序代码只会处在两个环境中,一是全局环境,二是局部环境。在浏览器中,全局环境中的this指的是window这个全局对象;而局部环境指的是代码块内部,这里的代码块内部包括:函数内部、字面量声明的对象的代码块内部等。

而执行上下文有3种类型:

  • 一是全局执行上下文,任何不在函数内部的代码都在全局执行上下文中。全局执行上下文会做两件事:创建一个全局的window对象(宿主环境为浏览器的情况下),并设置this的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
  • 二是函数内部的执行上下文,每当函数被调用的开始阶段,该函数的执行上下文会被创建,当函数执行完毕的时候执行上下文会被销毁。所以,同一个函数被多次调用的话,每一次都会创建一个执行上下文。
  • 三是eval()函数内部的执行上下文,执行在eval()函数内部的代码也会有属于它的执行上下文,但是在实际开发者中eval()很少被使用到,所以在此处不再深入探讨。

三、什么是执行栈?

答:执行栈,也就在其他编程语言中所说的“调用栈”,是一种拥有LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。

每当Javascript脚本开始执行的时候,它会创建一个全局执行上下文,并把全局执行上下文压入栈顶(此时的执行栈只存有全局执行上下文,可以理解全局执行上下文既在栈顶又在栈底),而当执行执行代码遇到函数调用的时候(指的是执行一个函数,而不是定义一个函数)会创建一个新的执行上下文并压入栈顶,然后进入函数内部执行函数内部的代码,如果函数内部又调用了其他函数,那么同样会创建一个新的执行上下文并压入栈顶,直到函数执行完毕,并会按照顺序从栈顶开始依次出栈。举个例子:

let a = 'Hello World!';
function first(){
	console.log('处在 first 方法内部');
	second();
	consle.log('第二次在 first 方法内部');
}
	
function second(){
	console.log('处在 second 方法内部');
}

first();
console.log('处在全局执行上下文内部');
 

执行结果是:

处在 first 方法内部
处在 second 方法内部
第二次处在 first 方法内部
处在全局执行上下文内部
 

相关的执行栈逻辑可以参看该图:

执行栈解析

通过上面的代码示意图可以看出,脚本执行最开始时会将一个全局执行上下文压入栈内,然后遇到函数first()被调用,将first()执行栈压入栈底,在first()函数执行的过程中,first()内部调用函数second(),所以此时再将second()执行栈压入栈底,second()函数执行完毕就开始进行出栈操作,将second()执行栈出栈,而后函数first()执行完毕,将first()执行栈出栈,最后整体代码执行完毕将全局上下文栈进行出栈。
通过上面的解释和例子,可以帮助我们很好的理解函数的调用顺序,尤其是存在着嵌套关系的函数之间的调用顺序(当然不要让两个函数互相调用,那样的话调用不会结束,浏览器可能崩溃)。

四、怎么创建执行上下文?

答:创建执行上下文有2个阶段:创建阶段执行阶段

创建阶段

在创建阶段会发生三件事:

  1. this 值的决定,即我们所熟知的 This 绑定。
  2. 创建词法环境组件。
  3. 创建变量环境组件。

其中,全局执行上下文的this指向window(浏览器环境下);而函数执行上下文的this取决于调用它的方式。如果函数是在被直接调用,那么它的this会被设置为window(严格模式下被设置为undefined),否则当它被对象调用时将会指向该对象,如上面的函数second()被函数first()调用,它的this指向first()。

创建词法环境组件和创建变量环境组件这两点比较晦涩,笔者也不能完全理解,所以不再赘述,以免出错误导读者。

执行阶段

这是整篇文章中最简单的部分。在此阶段,完成对所有这些变量的分配,最后执行代码。

参考资料、建议阅读的文章

  1. [译] 理解 JavaScript 中的执行上下文和执行栈

回复

我来回复
  • 暂无回复内容