ECMA-262-3 详解:1、执行上下文

1,346 阅读3分钟

从来没有深入了解ECMA,网上找了一下,发现早在2010年就有大佬 Dmitry Soshnikov 总结了ECMA中的核心内容,我这里只是翻译记录,加深自己的印象。文章原文来自 ECMA-262-3 in detail. Chapter 1. Execution Contexts

介绍

这篇文章中将会涉及到ECMAScript的执行上下文和与之相关的可执行代码类型。

定义

每一次当控件进入ECMA的可执行代码区域,那么控件就进入到了执行上下文。

执行上下文(缩写为-EC)是ECMA-262规范用于可执行代码的典型和区分的抽象概念。

从技术实施的角度来看,这个标准并没有定义EC的准确结构和类型。这是ECMAScript引擎实施标准的问题。

从逻辑上来说,一组活动的执行上下文形成一个堆栈。栈底 始终 都是一个全局上下文(globalContext),栈顶则是当前(活动)执行上下文。在进入和退出各种EC的时候修改(推入/推出)堆栈。

可执行代码的种类

对于可执行上下文的抽象概念,可执行代码的类型的概念是与之相关的。说道代码类型,在某些时候,是可以表示执行上下文的。

例如,我们将执行上下栈定义为一个数组

ECStack = [];

每一次进入一个函数的时候(即使这个函数是一个递归函数或者是一个构造函数),以及内置的 eval 函数工作时,堆栈都会被推入。

全局代码

这类代码在程序初始化的时候执行:例如加载外部的js文件或者通过本地的内联js代码(包含在 <script></script> 中的代码)。全局代码不包含任何函数体中的代码。

初始的时候(程序开始运行的时候), ECStack 看起来是这样:

ECStack = [
	globalContext
];

函数代码(功能代码)

当进入函数代码时候(各种函数,构造函数,递归,IIFE,eval等), ECStack 推入一个新的元素。需要注意的是,具体函数的代码不包括内部函数的代码。

举个例子,我们运行一个一次递归的函数

(function foo(flag){
	if (flag) return;
	foo(true);
})(false);

然后,对 ECStack 进行如下修改:

// 第一次运行 foo 函数,即 foo 的立即执行
ECStack = [
	<foo> functionContext,
	globalContext
];

// 运行 foo 函数的递归
ECStack = [
	<foo> functionContext - recursively,
	<foo> functionContext,
	globalContext
];

函数的每个返回(可以理解为这段函数执行完成)都会退出当前的执行上下文,并且 ECStack 进行相对应的退出,这是堆栈的客观规律。上面这段代码(包含一次递归)执行完成后, ECStack 将再次只包含 globalContext ,知道程序结束。

抛出但未捕获的异常也可能会导致一个或者多个执行上下文退出。

(function foo(){
	(function bar(){
		throw '退出 bar 和 foo 的执行上下文';
	})();
})();

虽然上面的代码是推出了栈,但是,程序出错,可能就不执行了。

Eval代码

eval 代码让事情变得更加有趣了。在使用 eval 的情况下,关于 调用上下文 的概念产生了。即,在一个上下文中调用了 eval 函数。

eval 进行的操作,比如定义一个函数或者函数声明的时候会直接影响了调用上下文。

// 影响了全局上下文
eval('var x = 10');
(function foo(){
	// 影响了 foo 函数的局部上下文,b 是函数的局部变量
	eval('var y = 20');
})()

alert(x); // 10
alert(y); // y is not defined

Note:在 ES5 的严格模式中, eval 不会影响调用上下文,取而代之的是,会对沙箱代码进行评估。

上面的例子会对 ECStack 进行如下修改:

ESStack = [
	globalContext,
]

// eval('var x = 10');
ECStack.push({
	context: evalContext,
	callingContext: globalContext
})

// eval('var x = 10'); 执行完成
ECStack.pop();

// foo function 执行
ECStack.push(
	<foo> functionContext
)

// eval('var y = 20');
ECStack.push({
	context: evalContext,
	callingContext: <foo> functionContext
})

// eval('var y = 20'); 执行完成
ECStack.pop();

// foo function 执行完成
ECStack.pop();