本章我们一起讨论一下ECMAScript的执行上下文及相关可执行代码的各种类型。so...什么是执行上下文?我们来看看定义:
每次当控制器转到ECMAScript可执行代码的时候, 即会进入到一个执行上下文。 执行上下文(以下我们且称EC)是ECMA-262标准里的一个抽象概念,用于可执行代码概念进行区分。活动的执行上下文组在逻辑上组成一个堆栈。 堆栈底部永远都是全局上下文(global context), 而顶部就是当前EC。堆栈在EC类型进入和退出上下文的时候被修改(pop or push)。
可执行代码类型
可执行代码的类型这个概念与执行上下文的抽象概念是有关系的。 在某些时刻, 可执行代码与执行上下文
完全有可能是等价的。下面我们且定义EC堆栈是一个数组:
var ECStack = [];
每次执行function时(递归调用或作为构造函数调用)或内置的eval函数工作时,这个堆栈就会被压入。
全局代码
全局代码是在“程序”级处理的,比如外部引入的js文件、本地标签中的代码。而任何function体内的代码都不是全局代码。
在初始化阶段,ECStack是这样的:
ECStack = [
globalContext
]
函数代码
当进入函数代码的时候,ECStack被压入新元素。但不包括该函数的内部函数代码。比如,我们定义一个函数,使函数递归一次:
(function func(bar){ if(bar) { return ; } arguments.callee.call(null ,true); })();
第一次执行上面代码时,ECStack是这样的:
ECStack = [functionContext, globalContext ]
第二次(递归调用)执行的时候,ECStack变成这样了:
ECStack = [functionContext, functionContext, globalContext ]
当程序 return 的时候,就会退出当前EC,对应的ECStack会被弹出。栈指针会自己调整位置,这是典型的堆栈。一个抛出的异常如果没被截获的话也有可能从一个或多个执行上下文退出。 相
关代码执行完以后, ECStack只会包含全局上下文(global context), 一直到整个应用程序结束。
eval代码
我们这里不讨论eval性能及其他问题。这里我们只关注上下文相关,它有一个概念:calling context(调用上下文),例如:eval函数调用所产生的上下文会影响调用上下文:
eval('var x = 1'); (function(){ eval('var y = 2'); })(); console.log(x) // 1 console.log(y) // y is not defined
整个执行过程,ECStack变化如下:
ECStack = [ globalContext ] //eval('var x = 1') ECStack.push(evalContext); ECStack = [ evalContext , callingContext : globalContext ]; // eval exited context ECStack.pop(); //anonymous function ECStack.push(functionContext); //eval('var y = 2') ECStack.push(evalContext); //eval exited context ECStack.pop(); // anonymous function exited context ECStack.pop();
参考:
本文参考了大叔的“执行上下文”章节,在此基础上做了一些调整。