概要
在这篇文章中,我们将向大家介绍ECMAscript的可执行上下文以及相关的可执行上下文的类型
定义
每当控制器进入ECMAscript可执行代码的时候,控制器就进入了一个可执行上下文。可执行上下文(简称EC)是一个抽象的概念,在ECMA262中用他来区分不同类型的可执行代码
标准规范中并没有从技术实现的角度来定义执行上下文的具体结构和类型;这是实现标准的ECMAScript引擎所要考虑的问题。
在逻辑上,活动的可执行上下文组成了一个堆栈,在堆栈的底部是一个全局的可执行上下文,顶部是当前可执行的上下文。各种不同类型的可执行上下文在进入或者离开堆栈的时候修改堆栈。
可执行上代码的类型
可执行上下文的类型和可执行代码是相关的。有的时候,提到可执行代码的类型的时候,就是表示可执行上下文。
例如:我们定义一个可执行上下文的堆栈用数组表示
ECStack = [];
每次控制器进入一个函数(即使该函数作为递归调用或者作为构造器),都会发生压栈的操作。内置的eval 函数工作的时候也是如此。
全局代码
这类代码是在程序级别上被处理的。例如:加载外部的js文件以及内联的js代码(被包含在script标签里面的) 全局代码是不包括任何函数内的代码。
在初始化的时候(程序开始),ECstack如下所示:
ECStack = [
globalContext
];
函数代码
一旦控制器进入函数代码,ECstack 被压入了新的元素。要注意的是:实体函数代码不包含任何内部函数代码。
如下图所示:我们调用函数,该函数递归调用自己一次。
(function foo(flag) {
if (flag) {
return;
}
foo(true);
})(false);
之后ECstack被修改成下面这样
// first activation of foo
ECStack = [
<foo> functionContext
globalContext
];
// recursive activation of foo
ECStack = [
<foo> functionContext – recursively
<foo> functionContext
globalContext
];
每次函数返回,退出当前活动的执行上下文时,ECStack就会被执行对应的退栈操作——先进后出——和传统的栈实现一致。同样的,当抛出未捕获的异常时,也会退出一个或者多个执行上下文,ECStack也会做相应的退栈操作。待这些代码完成之后,ECStack中就只剩下一个执行上下文(globalContext)——直到整个程序结束。
Eval代码
说到eval代码就比较有意思了。这里要提到一个叫做调用上下文的概念,比如:调用eval函数时候的上下文,就是一个调用上下文,eval函数中执行的动作(例如:变量声明或者函数声明)会影响整个调用上下文:
eval(‘var x = 10’);
(function foo(){
eval(‘ var y = 20’);
})();
alert(x); // 10
alert(y); // ”y” is not defined
ECStack会被修改为:
ECStack = [
globalContext
];
//eval(‘var x = 10’);
ECStack.push(
evalContext,
callingContext: globalContext
);
// eval exited context
ECStack.pop();
//foo function call
ECStack.push( functionContext);
//eval(‘ var y = 20’);
ECStack.push(
evalContext,
callingContext: functionContext
);
//return from eval
ECStack.pop();
//return from foo
ECStack.pop();
在1.7以上版本SpiderMonkey的实现中(Firefox,Thunderbird浏览器内置的JS引擎),允许在调用eval函数的时候,将调用上下文作为第二个参数传递给eval函数。因此,如果传入的调用上下文存在的话,就有可能会影响该上下文中原有的私有变量(在该上下文中声明的变量):
function foo(){
var x = 1;
return function() { alert(x); }
};
var bar = foo();
bar(); // 1
eval(‘x = 2’, bar); //传递上下文,影响了内部变量“var x”
bar(); // 2
总结
这些基本理论对于后面执行上下文相关的细节(诸如变量对象、作用域链等等)分析是非常必要的。