重学JavaScript(3) - 执行上下文栈

133 阅读3分钟

提问:JavaScript的代码是怎么运行的?

首先我们先看两个例子:

例子1

var foo = function () {

    console.log('foo1');

}

foo();  // foo1

var foo = function () {

    console.log('foo2');

}

foo(); // foo2

例子2

function foo() {

    console.log('foo1');

}

foo();  // foo2

function foo() {

    console.log('foo2');

}

foo(); // foo2

例子1中的输出结果我们可以理解,按照代码一步一步执行输出的,但是例子2却和我们想的不同!

*** 这是因为JavaScript代码一不是行一行的顺序执行的,而是一段一段的分析执行的,每次执行一段代码前,都会做一些”准备工作“,例如例子2中的函数提升等。***

那JavaScript引擎是怎么分段执行的呢?又做了哪些“准备工作”呢?

JavaScript引擎通过运行环境将代码分段执行的,主要分为三类:

  • 全局环境:JavaScript程序初始化时,会进入该环境
  • 函数环境:执行函数时,会进入该环境
  • eval:不建议使用

每当进入一个运行环境时,都会进行一系列的“准备工作”,专业称呼就是执行上下文(Execution Context,EC)。一个JavaScript程序有很多个函数,产生很多的执行上下文,如何管理这些执行上下文,就是通过执行上下文栈(Execution context stack,ECS)。

执行上下文可以理解为函数执行的环境,每一个函数执行时,都会给对应的函数创建这样一个执行环境。

执行上下文栈

当JavaScript引擎解析代码时,首先遇到全局环境,会向执行上下文栈中push一个全局上下文,后面如果遇到函数环境,就依次将函数的执行上下文push到执行上下文栈中,当函数执行完,其对应的执行上下文也会从栈中pop出,直到最后一个全局上下文被pop出。

代码演示如下:

var name = '旺财';

function changeName(){
	var anotherName = "旺旺";
	
	function swapName(){
		var temp = anotherName;
		anotherName = name;
		name = temp;
	}
	
	swapName();
}

changeName();

上面代码执行时,首先会向ECS中push一个全局上下文,用globalcontent表示。

捕获.PNG

全局上下文入栈后,开始执行代码,直到遇到changeName()时,又创建一个执行上下文,并将其push到栈中。

捕获2.PNG

changeName()的执行上下文入栈后,JavaScript的引擎开始执行该函数中的代码,直到遇到swapName(),再次创建执行上下文,并将其push到栈中。

捕获3.PNG

swapName()的执行上下文入栈后,JavaScipt的引擎开始执行该函数中的代码,没有其他的函数创建执行上下文。因此执行完成后,从栈中pop出swapName()的执行上下文

捕获2.PNG

继续执行changeName()的可执行代码,没有遇到没有其他的函数创建执行上下文。因此执行完成后,在栈中pop出changeName()的执行上下文。

捕获.PNG

全局上下文等管理浏览器或窗口后出栈。

整体流程如下:

执行上下文栈.drawio.png

参考

  1. 前端基础进阶(二):执行上下文详细图解