<JavaScript深入> 执行上下文栈

JavaScript 深入系列 #5

<JavaScript深入> 执行上下文栈
Photo by Greg Rakozy / Unsplash

顺序执行?

如果要问到 JavaScript 代码执行顺序的话,想必写过 JavaScript 的开发者都会有个直观的印象,那就是顺序执行,毕竟:

var foo = function () { console.log('foo1');}

foo();  // foo1

var foo = function () { console.log('foo2') }

foo(); // foo2

实际上执行结果是这样的

var foo = function () { console.log('foo1');}

foo();  // foo2

var foo = function () { console.log('foo2') }

foo(); // foo2

刷过面试题的都知道这是因为 JavaScript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码的时候,会进行一个“准备工作”,比如第一个例子中的变量提升,和第二个例子中的函数提升。

可执行代码

这就要说到 JavaScript 的可执行代码(executable code)的类型有哪些了?

其实很简单,就三种,全局代码、函数代码、eval代码[1]

举个例子,当执行到一个函数的时候,就会进行准备工作,这里的“准备工作”,让我们用个更专业一点的说法,就叫做"执行上下文(execution contexts)"。

执行上下文栈

function fun3() {
    console.log('fun3')
}

function fun2() {
    fun3();
}

function fun1() {
    fun2();
}

fun1();

当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。

压入堆栈
[fun1] ->
[fun2,fun1] ->
[fun3,fun2,fun1] 

然后依次执行
fun3 -> fun2 -> fun1

执行完后,清空堆栈

  1. 和其他解释性语言一样,javascript 同样可以解释运行由javascript源代码组成的字符串,javascript 通过eval()来完成。eval() 其实是一个函数,是javascript很早版本中就已经存在,但是javascript设计者和解释器对其进行了许多的限制,看起来eval更像一个运算符。但是作为用于动态执行代码,eval存在这样的问题,解释器无法对动态代码做进一步的优化。Eval 只有一个参数,如果传入的不是字符串的话,它直接返回这个参数;如果是字符串,那么首先会进行编译,如编译失败则爆出语法错误;编译成功则执行代码。更主要的是eval可以使用调用它的变量作用域环境,也就是说它查找的变量或者新增的变量作用域跟代码的完全一样。eval 可以动态执行代码,并申明或者改变变量。 ↩︎