Execution Context in JavaScript
[JavaScript] 执行上下文 Execution Context
介绍
本文中,我们将分析ECMAScript的执行上下文以及相关的可执行代码的类型
先举个栗子🌰,为什么a和b表现大不相同,当引用一个函数或者变量时,解释器是如何以及从哪里找到它们的呢
b(); // Called b
console.log(a); // undefined
var a = "Hello World!";
function b() {
console.log("Called b");
}
定义
每当控制器执行到ECMAScript可执行代码时,控制器就进入执行上下文
执行上下文(缩写:EC)是ECMA-262规范用于区分可执行代码的抽象概念
该标准并没有对EC的结构和种类做出准确的定义
逻辑上,一系列活动的执行上下文形成一个堆栈,这个堆栈的底部总是一个全局上下文,顶部是当前活动的执行上下文,在不同的执行上下文间切换时(进入和退出各种EC),堆栈被修改(通过压栈或者退栈)
可执行代码的类型
可执行代码类型与执行上下文是相关的,说到代码类型,就是指执行上下文
例如,我们将执行上下文的堆栈定义为一个数组:
ECStack = [ ];
每次控制器进入一个函数(即使该函数被递归调用或作为构造函数),都会发生压栈的操作,内置eval函数也不例外
全局代码
这种类型的代码在程序级中处理,即加载外部.js文件或本地内联代码(在<script></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中就只剩下一个执行上下文(globalContext),直到程序结束
当抛出未捕获的异常时也可能会退出一个或多个执行上下文:
(function foo() {
(function bar() {
throw 'Exit from bar and foo contexts';
})();
})();
Eval代码
在这种情况下,存在一个调用上下文的概念,比如调用eval函数时的上下文
eval函数中所做的操作(如变量或函数定义)影响整个调用上下文:
// influence global context
eval('var x = 10');
(function foo() {
// and here, variable "y" is
// created in the local context
// of "foo" function
eval('var y = 20');
})();
alert(x); // 10
alert(y); // "y" is not defined
对于上面的示例,ECStack被修改为:
ECStack = [
globalContext
];
// eval('var x = 10');
ECStack.push({
context: evalContext,
callingContext: globalContext
});
// eval exited context
ECStack.pop();
// foo funciton call
ECStack.push(<foo> functionContext);
// eval('var y = 20');
ECStack.push({
context: evalContext,
callingContext: <foo> functionContext
});
// return from eval
ECStack.pop();
// return from foo
ECStack.pop();
在旧版SpiderMonkey(版本1.7及以前)实现中,可以将调用上下文作为eval函数的第二个参数传递,因此,如果上下文仍然存在,就有可能影响私有变量:
function foo() {
var x = 1;
return function () { alert(x); };
};
var bar = foo();
bar(); // 1
eval('x = 2', bar); // pass context, influence internal var "x"
bar(); // 2