Execution Context in JavaScript

in #execution6 years ago

[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

引用

ECMA-262-3 in detail. Chapter 1. Execution Contexts.