ECMA-262-3详解(1)执行上下文

原文地址

作者的话

有很多文章已经对ECMAScript的核心概念做了详尽解读。本系列文章翻译自Dmitry Soshnikov的个人网站,相信不少人已经看过原文或者译文。原文简洁易懂并且严谨,条理清晰地阐明了所有JavaScript开发者不得不深入理解的ECMAScript核心概念。重复翻译的原因主要是为了个人收藏、整理之用。初次翻译,技巧拙劣,如有不足,请不吝赐教。

正文

  1. 介绍
  2. 定义
  3. 可执行代码的类型

    1. 全局代码
    2. 函数代码
    3. Eval代码
  4. 结论

介绍

在这篇文章中我们将提到ECMAScript的执行上下文和与之相关的可执行代码类型。

定义

每次当控制转移到ECMAScript可执行的代码,控制就进入了一个执行上下文。

执行上下文(Execution context,缩写:EC)是ECMA-262标准使用的抽象概念,用来分类和区别一段可执行代码

标准没有从技术实现角度定义EC的准确结构和类型,这是ECMAScript引擎如何实现标准的问题。
逻辑上讲,活跃的执行上下文集合组成了一个栈。栈底是全局上下文(global context),栈顶是当前(活跃)执行上下文。在进入和退出不同的EC时,栈被修改(pop/push)。

可执行代码的类型

可执行代码的类型的概念与执行上下文的抽象概念相关。讲到代码类型,在特定时候,它可以指执行上下文。

举个例子,我们将执行上下文定义成一个数组。

ECStack = [];

每次进入一个函数,栈会被压入一个上下文(即使是函数被递归调用,或者作为构造函数被调用),在eval函数中也是如此。

全局代码

这个类型的代码在程序Program)层面被处理:比如加载的外部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代码更加复杂。在这种情况下,有一个概念叫调用上下文calling context),比如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

注意,在ES5的严格模式中,eval已经不会影响 调用上下文,而是在本地沙箱中运行代码。

对于上面的例子,我们有如下的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 实现(firefox)中,最多到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

然而,由于现代引擎的安全问题,它被修复而不在有意义。

ES2015+引入了一种新的代码类型-模块代码

结论

这些基础的理论需要被用来更深入地研究与执行上下文相关的细节,比如变量对象作用域链,这些描述可以在适当的章节被找到。

原始作者:Dmitry Soshnikov
原始发布时间:2009-06-26

    原文作者:freepoix
    原文地址: https://segmentfault.com/a/1190000015966386
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞