执行上下文栈 (Execution Context Stack)。
在 ECMASscript 中的代码有三种类型:global, function 和 eval。
每一种代码的执行都需要依赖自身的上下文。当然 global 的上下文可能涵盖了很多的 function 和 eval 的实例。函数的每一次调用,都会进入函数执行中的上下文,并且来计算函数中变量等的值。eval 函数的每一次执行,也会进入 eval 执行中的上下文,判断应该从何处获取变量的值。
注意,一个 function 可能产生无限的上下文环境,因为一个函数的调用(甚至递归)都产生了一个新的上下文环境。
function foo(bar) {}
// 调用相同的function,每次都会产生3个不同的上下文
//(包含不同的状态,例如参数bar的值)
foo(10)
foo(20)
foo(30)
一个执行上下文可以激活另一个上下文,就好比一个函数调用了另一个函数(或者全局的上下文调用了一个全局函数),然后一层一层调用下去。逻辑上来说,这种实现方式是栈,我们可以称之为上下文堆栈。
激活其它上下文的某个上下文被称为 调用者(caller) 。被激活的上下文被称为被调用者(callee) 。被调用者同时也可能是调用者(比如一个在全局上下文中被调用的函数调用某些自身的内部方法)。
当一个 caller 激活了一个 callee,那么这个 caller 就会暂停它自身的执行,然后将控制权交给这个 callee. 于是这个 callee 被放入堆栈,称为进行中的上下文 [running/active execution context]. 当这个 callee 的上下文结束之后,会把控制权再次交给它的 caller,然后 caller 会在刚才暂停的地方继续执行。在这个 caller 结束之后,会继续触发其他的上下文。一个 callee 可以用返回(return)或者抛出异常(exception)来结束自身的上下文。
如下图,所有的 ECMAScript 的程序执行都可以看做是一个执行上下文堆栈 [execution context (EC) stack]。堆栈的顶部就是处于激活状态的上下文。
当一段程序开始时,会先进入全局执行上下文环境 [global execution context], 这个也是堆栈中最底部的元素。此全局程序会开始初始化,初始化生成必要的对象 [objects] 和函数 [functions]。在此全局上下文执行的过程中,它可能会激活一些方法(当然是已经初始化过的),然后进入他们的上下文环境,然后将新的元素压入堆栈。在这些初始化都结束之后,这个系统会等待一些事件(例如用户的鼠标点击等),会触发一些方法,然后进入一个新的上下文环境。
有一个函数上下文“EC1”和一个全局上下文“Global EC”,下图展现了从“Global EC”进入和退出“EC1”时栈的变化:
ECMAScript 运行时系统就是这样管理代码的执行。如上所述,栈中每一个执行上下文可以表示为一个对象。让我们看看上下文对象的结构以及执行其代码所需的 状态(state) 。