原文(英译)地址:ECMA-262-3 in detail. Chapter 1. Execution Contexts.
在这一章里,我们将会讨论ECMAScript中的执行上下文(execution context)以及与它们相关的可执行代码(executable code)的类型。
每当控制转移到ECMAScript的可执行代码上时,它就进入了一个执行上下文。
执行上下文(Execution context,简称EC)是ECMA-262规范中使用的一个抽象概念,用于表示和区分一段可执行代码。
在标准中并没有从技术实现的角度定义EC的精确结构和类型;这是实现ECMAScript标准的引擎的问题。
逻辑上,一族激活的执行上下文符合栈(stack)的概念。栈的底部总是一个全局上下文,而顶部是一个当前(激活)的执行上下文,栈在进出不同的EC时改变(push/pop)。
可执行代码的类型 | Types of executable code
可执行代码的类型(type of an executable code)的概念与抽象概念执行上下文相关的。当说到代码的类型时,在某些时候可能指的就是执行上下文。
例如,我们将执行上下文的栈定义为一个数组:
ECStack = [];
每次进入一个函数时(即使是函数的递归调用或作为构造式调用时)栈就被pushed,在内建的eval函数工作中也是如此。
这类代码是在程序级别(level Program)中处理的,也就是说,在加载外部的js文件或局部的行内代码(在<script></script>
标签之间)时。全局代码不包括任何在函数体内部的代码。
初始化时(程序开始时),ECStack像这样:
ECStack = [
globalContext
];
在进入函数代码时(所有类型的函数),新元素push到ECStack中。需要注意的是,某一个具体函数的代码不包括它的内部函数的代码。
例如,我们看一个递归一次的函数:
(function foo(bar) {
if(bar) {return;}
foo(true);
}) (false);
在这段代码执行时,ECStack的改变如下:
//foo函数第一次激活时
ECStrack = [
<foo> functionContext
globalContext
];
// foo函数递归激活时
ECStrack = [
<foo> functionContext - recursively
<foo> functionContext
globalContext
];
每次离开当前执行上下文而从函数中返回时ECStack也据此pop一个层——连续的和从上到下的(译者按:先进后出)——就像栈的自然实现那样。当这个函数代码的工作结束时,ECStack再次回到只有globalContext的状态——直到程序结束。
一个抛出但未捕获的异常也可能退出一个或多个执行上下文:
(function foo() {
(function bar() {
throw 'Exit from bar and foo context';
}) ();
}) ();
eval代码中的情况就更有趣了。这种情况下,有一个叫调用上下文(calling context)的概念——即被调用的eval函数所在的上下文。
eval函数的行为,比如变量或函数声明,实际上影响的是调用上下文:
//影响全局上下文
eval("var x = 10");
(function foo() {
//而这里的'y'是在foo函数的局部上下文中创建的
eval("var y = 20");
}) ();
alert(x); // 10
alert(y); // "undefined"
注意:在ES5的严格模式下,eval已经不再影响调用上下文,而是在一个局部沙盒(local sandbox)中评估相应的代码。
上面的例子中我们得到如下的ECStack变化:
ECStack = [
globalContext
];
// eval('var x = 10');
ECStack.push(
evalContext,
callingContext: globalContext
);
// eval exited context
ECStack.pop();
// foo funciton call
ECStack.push(<foo> functionContext);
// eval('var y = 20');
ECStack.push(
evalContext,
callingContext: <foo> functionContext
);
// return from eval
ECStack.pop();
// return from foo
ECStack.pop();
在旧版本(到1.9为止)的SpiderMonkey(Firefox的js引擎)中,允许以第二个参数的形式将一个调用上下文传入到eval函数中,因此,如果上下文还存在,它可以影响那些在(在函数中声明的)私有变量(private variables)。然而,由于安全方面的考虑现代引擎修复了它。
这篇中的概念是进一步分析与执行上下文相关的细节(例如变量对象variable object,作用域链scope chain)的基础,这些细节将在后面的章节中涉及到。
这一章的第二部分是关于EMCAScript中的面向对象编程。在第一部分中我们讨论了OOP的基本理论并勾画出和ECMAScript的相似之处。在阅读第二部分之前,如果有必要,我还是建议首先阅读这一章的第一部分.基本理论,因为后面将会用到其中的一些术语。
这一章我们讨论ECMAScript中面向对象编程(object-oriented programming)的几个主要方面。由于这一主题已经在许多文章中谈论过,本章并不打算“老调重弹”,而是试图更多地着眼于这些过程内在的理论方面。尤其是,我们将研究对象创建的算法,看看对象间的关系(包括最基本的关系——继承)是如何实现的,并且给出一些讨论中将用到的准确定义(我希望这样能够打消一些术语和思路上的疑惑以及一些关于Javascript文章中OOP部分的常见的混淆)。
在这一章中我们来谈谈Javascript中被讨论最多的话题之一——关于闭包(closures)。事实上这个主题并不是新鲜的。然而我们在这里将试着更多从理论的角度去分析和理解它,然后我们还会看一下ECMAScript内关于闭包的内容。
在这章里我们讨论ECMAScript中的一个基本对象——函数。我们将会看到不同类型的函数如何影响一个上下文中的变量对象,以及这些函数的作用域链中都包含什么。我们将会回答像下面这样经常被问到的问题:“下面这两种创建函数的方式有什么区别吗(如果有的话,区别是什么呢)?”
正如我们从第二章.变量对象中了解到的,执行上下文的数据(变量,函数声明,函数形参)以变量对象的属性的方式储存。
许多程序员习惯于认为在编程语言中,this关键字是与面向对象编程紧密相关的,而且引用的是由构造式最新创建的对象。在ECMAScript中,这个概念也被实现了,然而我们将看到,在这里它不仅仅指向已创建的对象。
在程序中我们总是声明变量和函数然后用它们来搭建我们的系统。但是解释器(interpreter)是在哪里和以什么方式来找到我们的数据(函数,变量)的呢?
第1章:在这一章里,我们将会讨论ECMAScript中的执行上下文(execution context)以及与它们相关的可执行代码(executable code)的类型。