ECMA-262-5 词法环境:通用理论(四)--- 环境

环境

   这一章我们将介绍词法作用域的技术实现。此外,由于我们对高度抽象的实体进行操作并谈论词法作用域,再进一步的解释中我们会主要使用环境的概念而不是作用域。因为在ES5使用了这个术语"环境"是,还包括全局环境和函数的局部环境等。

   之前提过的,环境赋予表达式中的一个标识符意义。例如对于表达式“x+1”,如果没有指明任何关于这个表达式的环境信息,无法得知符号x所代表的含义,进而对这个表达式的求值也就无从谈起。(甚至对于符号“+”,如果它被视为一个简单加法函数的语法糖—add(x,1),这种情况下我们也应该提供名称add的含义)
   ECMAScript 通过调用栈(call-stack)模式来管理函数的执行,在这里又叫执行上下文栈(execution context stack)。下面来看下储存变量的一些通用方式。并且在拥有闭包和没有闭包的程序语言中,你将会看到一些有趣的不同点。

激活记录模型(Activation record model)

   回到没有一类函数(这种函数可以被作为参数传递—稍后会谈到它)或者不允许内联函数的时候,储存局部变量最简单的方式就是通过调用栈本身(译注:从定义角度来说,调用栈是一个方法列表,按调用顺序保存所有在运行期被调用的方法)。
   调用栈中有一个特殊的数据结构叫做激活记录,它被用来储存环境绑定(environment bindings)(译注:包括参数、局部变量和返回地址,也是编译器用来实现过程/函数调用的一种数据结构)。有时它也被叫做栈帧。
   每次函数被激活,它的激活记录(包括形参和局部变量)就会被push到调用栈上。也就是说,如果函数调用另一个函数(或者递归的调用它自己),那另一个栈帧也会被push到调用栈中。当函数返回时,这些激活记录就会从调用栈中被移除(意味着函数的所有局部变量也会被销毁)。这种模式也是诸如C语言所采用的。
举个例子:

void foo(int x) {
  int y = 20;
  bar(30);
}
 
void bar(x) {
  int z = 40;
}
 
foo(10);

   调用栈的变化过程

callStack = [];
 
// 函数"foo" 被激活
// 激活记录被push到栈
 
callStack.push({
  x: 10,
  y: 20
});
 
// 函数"bar" 被激活
//激活记录被push到栈
 
callStack.push({
  x: 30,
  z: 40
});
 
//"bar"被激活时栈的状态

console.log(callStack); // [{x: 10, y: 20}, {x: 30, z: 40}]
 
// 函数"bar" 结束
callStack.pop();
 
// 函数"foo" 结束
callStack.pop();

  下面这张图我们可以看到当函数bar被激活的时候,两个激活记录被push到调用栈。
call-stack
   ECMAScript中使用了逻辑完全相同的管理函数执行的方式。然而,ECMAScript拥有一些很重要的不同点。
   首先,正如我们知道的那样,对于调用栈的概念这里代表执行上下文栈,激活记录就是激活对象(ES3中)(译注:ES5中不存在激活对象,都统一到单个的环境模型中)。
  但是在这里,术语差异不那么重要。最主要的区别在于,当存在闭包时,ECMAScript在函数结束后不会将激活对象从内存中移除。一个很重要并且很常见的情形是,当闭包是某个使用父函数中创建的内部函数,同时这个内部函数被父函数作为返回值返回。
  这意味着激活对象不应该被存储在栈上(译注:因为栈数据机构的特性),而应该在堆中(通过动态内存分配的方式;有时候这种使用堆的语言被称为基于堆的语言,相对的,另一种叫基于栈的语言)。它会被一直存在堆中,等待某个闭包引用来自这个激活对象中自由变量。此外,储存不仅仅是单个的活动对象,在需要的情况下(对于那些嵌套的内联),所有的父级活动对象都要被储存起来。
  我们来看一例子:

var bar = (function foo() {
  var x = 10;
  var y = 20;
  return function bar() {
    return x + y;
  };
})();
 
bar(); // 30

  下面这张图从抽象层面阐明了基于堆的活动记录是如何表示的。我们看到若是在在函数foo中创建了闭包,之后当foo完成时,它的栈帧未从内存中移除,因为闭包中还拥有对它的引用。
call-stack-with-closures
   这些激活对象的理论中使用的术语之一是环境帧(environment frames)(类比与栈帧)。用这个术语来强调与栈帧实现的差异,如果从闭包中引用了环境帧的内容,环境帧在函数结束后将继续存在。此外,我们使用这个术语来强调更高度抽象的概念,从而不用关注底层的堆栈和地址结构。我们把这些都看作是环境的组成,而这些是如何实现的,就是接下来要讲的。

环境栈模型

  如上所述,相比于C,ECMAScript中拥有闭包和内部函数。此外,ECMAScript中的所有函数都是第一类函数。让我们回顾下这类函数的定义以及其他关于函数式编程的定义。我们将会看到这些概念与词法环境模型有着密切的关联。

  同时,我们将会明白闭包的问题(或者说下面要提到的函数式参数的问题)实际上是词法环境的问题。这也是为什么本节会花费篇幅来讨论函数式语言的基本概念。

一类函数(First-class functions)

   第一类函数是指函数可以像普通数据一样赋值给其他变量或者结构,可以在运行时用字面量创建,可以作为参数传递,也可以作为返回值从其他函数中返回。
  一个简单的例子:

// 在运行时动态的创建
//函数表达式,并绑定到
//标识符“foo”上
var foo = function () {
  console.log("foo");
};
// 把刚才创建的作为参数
//传递给另一个动态创建的
//立即执行函数,这个IIFE的
//执行结果再次绑定给“foo”
 
foo = (function (funArg) {
  // 激活开始创建的“foo”
  funArg(); // "foo"
  // 作为返回值返回的函数
  return funArg;
 
})(foo);

第一类函数也可以被细分更多子概念。

函数式参数和高阶函数

  • 当一个函数被作为参数传递,它被称为“函数式参数-funarg”,funarg一词是functional
    argument的缩写。
  • 反过来,一个使用funarg的函数被称为高阶函数-higher-order
    function (HOF),或者偏数理的叫法,称为操作符函数。
  • 若一个函数用另一个函数作为返回值,
    它被称为带函数值的函数,或者值函数( function-valued function)。

  有了这些概念,下面我们会看到一个被称作“函数式参数(funarg-problem)”的问题。同样我们很快将会知道,这个问题的解决方法就是闭包和词法环境。
  在上面这个例子中,函数foo就是一个传入匿名高阶函数(它通过形参funArg接受函数式参数foo)的函数式参数。这个匿名函数反过来将函数作为返回值,再次返回foo函数本身。这些函数都能在一类函数的定义中找到对应的类别。

自由变量

  与一类函数相关并且我们应该了解的另一个重要概念—自由变量,译注:如果有兴趣可以参考SICP相关章节

自由变量就是函数使用的,既不是函数局部变量,也不是函数参数的变量。

  换句说话,自由变量就是本函数中使用,但声明不在当环境中,而是在某些外部环境中。
  看一眼下面这个例子

// Global environment (GE)
var x = 10;
function foo(y) {
  //通过函数“foo”创建的环境(E1)
  var z = 30;
 
  function bar(q) {
    // 通过函数“bar”创建的环境( (E2)
    return x + y + z + q;
  }
 
  // 将“bar”返回到外部
  return bar; 
}
var bar = foo(20); 
bar(40); // 100

  这个例子中我们看到了三个环境:GE,E1和E2,分别对应全局对象,foo函数和bar函数。
也就是说,对于函数bar,变量x,y,z都是自由的—它们既不是函数参数,也不是函数bar的局部变量。
   注意,这里函数foo本身没有使用自由变量。但由于函数bar用到了变量x,并且函数bar是函数foo在执行期间创建的,因此后者应该保存父环境的绑定信息—为了将关于x绑定的信息进一步传递给给深层的内部函数(这里是函数bar)。
   执行bar(40)的时候得到了预期的正确结构100,这意味着bar以某种方式记住函数foo激活的环境(在其中创建了内部函数bar),即使foo的上下文已经完成。再次重复,这也是ECMAScript与C这种基于栈的激活记录模型的不同之处。
   很显然,如果我们允许所有嵌套的内部函数都拥有静态(词法)作用域,同时,所有这些函数都是一类函数,那么我们就必须把函数使用的所有自由变量在函数创建时就全部储存起来。

环境定义

  最简单直接实现上面这个算法的方法是把(内部函数的)父函数的完整环境信息保存下来。随后,当内部函数激活(这个例子中,在bar函数激活时),我们先去创建自己的环境,包含函数参数和它的内部变量,再去建立一个外部环境的属性,将刚才保存的环境(译注:父函数环境或全局环境)设置为这个属性的值—在这个属性的值中可以找到自由变量。
  对于术语环境,它可以用于描述一个单独的绑定对象(译注:这里对象指激活对象,亦C中激活记录),也可以对应于嵌套结构上所有绑定对象的列表。在后面的例子中,我们可以将绑定对象称为环境的框架( frames of the environment)(译注:在上面我们提到过一个environment frames,即环境帧的概念,两个术语中都有frames一词,但两处翻译不同,这里是有意为之。上面的frames是对应于栈帧的概念,方便理解。在这里,翻译成框架,是因为在SICP中,关于环境3.2节的中文版中用了该词)。从这个角度看:

一个环境就是框架的一个序列( sequence of frames)每个框架是包含着一些绑定的一个记录(可能为空),这些记录将一些变量名字关联于对应的值。

  注意,由于这是一个通用定义,我们使用了抽象概念记录而不是制定确切的实现结构—它可以一个堆中的散列表,或者一个栈存储器,甚至是虚拟机的寄存器等。
  例如,上面例子中的环境E2有三个框架,自身的bar,foo以及全局的global。环境E1包含两个框架:foo(自身的)以及全局框架global。全局环境GE只包含一个框架,也就是它自己,全局框架。
environment
  一个框架内,任何变量至多只能有一个绑定。每一个框架中都会有一个指针,指向其外围环境。对于全局框架来说,它指向的外部环境是null。一个变量相对于某个特定环境的值,也就是在这一环境中,包含着该变量的第一个框架里这个变量的绑定值。如果在序列中没有找到变量的绑定,那么就说这个变量在该环境中是无绑定的(访问会出现ReferenceError)。

译注:在这里,关于环境的概念原作者是引用了SICP 3.2 的内容,为了方便理解,我在这里给出SCIP关于环境的完整说明:
   一个环境就是框架的一个序列,每个框架是包含着一些约束的一个表格(可能为空),这些约束将一些变量名字关联于对应的值(在一个框架里,任何变量至多只能有一个约束)。每个框架还包含着一个指针,指向这一框架的外围环境。如果框架是全局的,那么它将没有外围环境。一个变量相对于某个特定环境的值,也就是在这一环境中,包含着该变量的第一个框架里这个变量的约束值。如果在序列并不存在这一变量的约束,那么我们就说这个变量在该特定环境中是无约束的。

var x = 10;
(function foo(y) {   
  // 使用外部环境的绑定x
  console.log(x);
  //内部绑定y
  console.log(y); // 20 
  // 外部环境绑定z
  console.log(z); // ReferenceError: "z" is not defined
 
})(20);

   再回到作用域的概念,这个环境框架的序列(从另外一个角度看,也可以叫做环境的链式列表)形成了我们可以称为作用域链的东西。不用惊讶,这正是你可能熟悉的ES3中的术语—一个作用域链。
注意,一个环境可以同时作为几个内部环境的外层环境:

// Global environment (GE)
 
var x = 10; 
function foo() {
  // "foo" environment (E1) 
  var x = 20;
  var y = 30; 
  console.log(x + y);
}
 
function bar() {
  // "bar" environment (E2)
  var z = 40;
  console.log(x + z);
}

  用伪代码表示:

// global
GE = {
  x: 10,
  outer: null
};
 
// foo
E1 = {
  x: 20,
  y: 30,
  outer: GE
};
 
// bar
E2 = {
  z: 40,
  outer: GE
};

  下面这张图展示了它们的关系:
common-parent-environment
  此外,环境E1关于x的绑定遮蔽了全局框架中的同名绑定x。

函数创建和应用规则

  在这一部分将介绍创建和应用(调用)函数的通用规则:
  函数是创建在一个给定的环境中。生成的函数对象是一组由代码(函数体)和指向函数创建时那个环境的指针组成的对偶。
译注:在这里关于函数对象的概念,原作者亦引用自SICP 3.2.1,为方便理解,可以观看原文。由于原文中的术语以scheme为准,与ES有一定差异,此处篇幅不足以进行完整解释。补充一下,这里函数对象的生成是由解释器完成,是在函数创建之后对函数表达式进行求值生成的
  看下面这段代码:

// global "x"
var x = 10;
 
// 函数"foo"创建在全局环境下

function foo(y) {
  var z = 30;
  console.log(x + y + z);
}
通过伪代码来看生成的函数对象

// create "foo" function
 
foo = functionObject {
  code: "console.log(x + y + z);"
  environment: {x: 10, outer: null}
};

  再看下面这张图,展示了函数对象的内容
function-object
  注意,函数中引用了创建函数的环境,而环境框架中也存在关于这个函数对象的绑定。

  环境模型说明:在将一个函数应用于一组实际参数时,将会建立起一个新环境,其中包含了将所有形式参数与对应的实际参数,以及函数局部变量进行绑定的框架,该框架的外围环境就是函数对象指针指向的环境(译注:即函数对象指向的那个)。随后就在这个新环境下求值这个函数(即代码)

  将实参应用于形参:

// 函数“foo”的形参被应用于实参20

foo(20);

  相应的伪代码如下:
/ 创建一个新的将所有 
// 形参绑定于实参以及局部变量绑定的框架
 
fooFrame = {
  y: 20,
  z: 30,
  outer: foo.environment
};
 
// 求值函数“foo”的体

execute(foo.code, fooFrame); // 60

  再看下面这张图:
function-application
  由上面所介绍的直接给出了闭包的定义

闭包

  先来看看闭包定义:

闭包是由函数代码和函数的创建环境的组成的对偶。

  如上所述,闭包被发明是为了解决函数式参数- “funarg problem” 的问题,下面来看它的具体细节。

函数式参数问题

  函数式参数问题分为两个子问题,它们与作用域,环境和闭包的概念直接相关。
  首先我们看第一类,一个内部函数作为返回值从它的父函数被返回。当这个内部函数使用了来自创建它的父函数的自由变量,在父函数运行结束后(父函数的环境被销毁),这个返回的内部函数该如何继续访问那些父函数中的自由变量?

  看下面例子:
(function (x) {
  return function (y) {
    return x + y;
  };
})(10)(20); // 30

  这一点我们已经知道了,在词法作用域中,会将函数的的上层环境框架存在堆中—这就是这个问题的答案。在这里,那种将绑定储存在堆栈上(比如c)的策略不再使用。让我们再重复一遍,这个保存的代码块和环境—是一个闭包。

  第二类就是一个使用自由变量的函数被作为参数传递给另外一个函数时,这个参数函数使用的自由变量应该去哪查找,是这个参数函数定义时所在的作用域还是执行这个参数函数所在的作用域。
  看这个例子:

var x = 10;
 
(function (funArg) {
 
  var x = 20;
  funArg(); // 10, not 20
 
})(function () { // 创建一个参数函数
  console.log(x);//自由变量x
});

  第二类问题和我们在这一章开始关于选择词法作用域和动态作用域讨论有关。而在这里我们已经知道了—但再次强调,词法作用域就是答案。我们应该准确的保存词法变量以避免这种歧义。最后,这个保存的词法变量和我们的函数代码—就是所谓的闭包。(译注:上面两个问题的答案,都是通过同一种方式,在函数创建时就将上层环境(环境的框架集合)和函数的代码保存起来,这种策略实现就是闭包,跟据函数创建的规则,我们看到 在ECMAScript中,所有的函数都是闭包,因为它们都是在创建的时候就保存了上层环境 (不管这个函数后续是否会执行 )
  所以最后,我们了解到了什么?一类函数,闭包和词法环境的概念之间都是密切相关的。而词法环境正是用于实现闭包和静态作用于的通用技术。

  到这里,我们已经提到了ECMAScript正是使用的环境框架模型。但是,下一章我们会讨论在ES中的具体术语。

  关于闭包的具体解释可以去了解ES3版本关于闭包的介绍。

  为了完整性,我们还会继续阐述在其他一些语言中使用的某种环境实现。

组合环境框架模型

   务必记住,想要深入的了解一些具体技术(例如ECMAScript),应该首先了解通用的理论机制,并去看看其他语言是如何实现该技术的。我们会看到这些通用机制(如环境)在许多类似的语言都有出现,从而帮助你理解。当然,不同语言的具体实现会有稍许不同。这一部分专门介绍诸如Python,Ruby和Lua等语言的环境实现。
  一种保存所有自由变量的方法就是创建一个大的环境框架,里面包含所有来自不同外层环境但只是有用到的自由变量。
显然,如果某些自由变量不会被内部函数用到,那么也不需要保存它们。考虑下面这个例子:

// global environment
 
var x = 10;
var y = 20;
 
function foo(z) {
 
  // 函数"foo"的环境
  var q = 40;
 
  function bar() {
    // 函数" bar "的环境
    return x + z;
  }
  return bar;
 
}
// creation of "bar"
var bar = foo(30);
 
// 应用bar函数
bar();

  我们看到没有一个函数用到全局变量y。因此,无论是在foo还是bar的闭包中,都不会保存这个变量。
  全局变量x虽然没有在函数foo中使用,但正如我们上面提到的,x需要被保存在foo的闭包中,因为里面的内部函数bar用到了,而bar函数则会去它创建的环境中取x的信息(即从foo函数的环境中)。
  foo函数内的变量和全局变量x的情况一样—没有任何内部函数需要它,所以它不需要保存在bar的闭包中。而变量z很显然会保存在bar的闭包中。
  因此,我们创建了一个简单的关于函数bar的环境框架,里面包含需要的全部自由变量:

bar = closure {
  code: <...>,
  environment: {
    x: 10,
    z: 30,
  }
}

  在Python 中就采取了相似的模型。每个函数会将一个环境框架保存到__closure__(反映了词法环境的本质 )属性上。全局变量不会包含在这个框架上,因为它们始终都能在一个全局框架中找到。没有用到变量同样也不会保存到__closure__上。下面看个例子:

// Python环境例子
 
// global "x"
x = 10
 
// global "foo" function
def foo(y):
 
    # local "z"
    z = 40
 
    # local "bar" function
    def bar():
        return x + y
 
    return bar
 
// create "bar"
bar = foo(20)
 
// execute "bar"
bar() # 30
 
// 函数bar的环境被保存在__closure__属性上
//
// 它里面只包含{"y": 20};
// “x”不在__closure__上,是因为它始终在全局框架中可以找到
// “z”也不在,因为它没有用到

barEnvironment = bar.__closure__
print(barEnvironment) # tuple of closure cells
 
internalY = barEnvironment[0].cell_contents
print(internalY) # 20, "y"

  注意,即使在使用eval的情况下,python也不会保存未使用的变量,即使事先无法知道,变量是否在上下文中被使用。下面这个例子中,内部函数baz捕获了自由变量x,而bar则没有:

def foo(x):
 
    def bar(y):
        print(eval(k))
 
    def baz(y):
        z = x
        print(eval(k))
 
    return [bar, baz]
 
// create "bar" and "baz" functions
[bar, baz] = foo(10)
 
// "bar" does not closure anything
print(bar.__closure__) # None
 
// "baz" closures "x" variable
print(baz.__closure__) # closure cells {'x': 10}
 
k = "y"
 
baz(20) # OK, 20
bar(20) # OK, 20
 
k = "x"
 
baz(20) # OK, 10 - "x"
bar(20), # error, "x" is not defined

  ECMAScript相反,它会有一个链式的环境框架模型,用来管理这种情况(译注:同样还有with这种语法)。

function foo(x) {
  function bar(y) {
    console.log(eval(k));
  } 
  return bar; 
}
 
// create "bar"
var bar = foo(10);
 
var k = "y";
 
// execute bar
bar(20); // OK, 20 - "y"
 
k = "x";
 
bar(20); // OK, 10 - "x"

  此外,关于Python闭包的详细解释请参阅 Python code-article
  对于链式环境框架(ECMA)和单环境框架(Python)而言。
  ECMAScript使用的这种链式环境框架模型优化了函数创建的时刻,但是在标识符解析式,需要遍历整个作用域链,考虑所有环境框架(直到找到所需的绑定或者抛出ReferenceError )。

  同时,对于单个环境框架模型(所有的标识符都是在最近的单个框架中进行解析而没有作用域链的查找)它优化了函数执行的时刻。但这需要更复杂的函数创建算法,解析器需要解析所有的内部函数并确定保存哪些变量而不保存哪些。

  此外,上面关于ECMAScript 的结论仅符合ECMA-262-5 规范。在实际上,ES引擎可以优化ECMAScript实现并仅保存需要的变量。这部分会在下一章介绍。

  还需注意,严格来讲组合框架也可能不是单一框架模型。这意味着,虽然组合框架被优化成里面包含了来自若干父框架的绑定,但是环境可以包含一些额外的框架。Python也是如此,在执行时候它有一个自己的激活框架,即通过__closure__访问的组合框架,但是环境中还包括全局框架。

  编程语言Ruby也是使用单框架模型,它会捕获所有但仅仅是在闭包创建时就存在的自由变量。

# Ruby lambda closures example
 
# closure "foo", which has
# free variable "x"
  
foo = lambda {
  print x
}
 
# define the "x"
x = 10
 
# second closure "bar" with
# the same body - it also
# refers free variable "x"
 
bar = lambda {
  print x
}
 
bar.call # OK, 10
foo.call # error, "x" is not defined

  然而,Ruby会保存所有存在的变量,上面的例子可以用eval来解析一个未使用的变量:


k = "y"
 
foo = lambda { |x|
  lambda { |y|
    eval(k)
  }
}
 
# create "bar"
bar = foo.call(10)
 
print(bar.call(20)) # OK, 20 - "y"
 
k = "x"
 
print(bar.call(20)) # OK, 10 - "x"

  一些语言,例如Lua(也有一个单环境框架模型)允许在函数运行时动态的设置所需要环境,考虑下面的Lua例子:

-- Lua environments example:
 
-- global "x"
x = 10
 
-- global function "foo"
function foo(y)
 
  -- local variable "q"
  local q = 40
 
  -- get environment of the "foo"
  fooEnvironment = getfenv(foo)
 
  -- {x = 10, globals...}
  print(fooEnvironment) -- table
 
  -- "x" and "y" are accessible from here,
  -- since "x" is in the environment, and
  -- "y" is a local variable (argument)
  print(x + y) -- 30
 
  -- and now change the environment of the "foo"
  setfenv(foo, {
     -- use reference to "print" function,
     -- but give it another name
    printValue = print,
 
    -- reuse "x"
    x = x,
 
    -- and define a new "z" binding
    -- with value of the "y"
    z = y
 
  })
 
  -- use new bindings
 
  printValue(x) -- OK, 10
  printValue(x + z) -- OK, 30
 
  -- local variables are still accessible
  printValue(y, q) -- 20, 40
 
  -- but not other names
  printValue(print) -- nil
  print("test") -- error, "print" name is nil, can't call
 
end
 
foo(20)

##总结
  在这里我们已经完成了通用理论的介绍。下一部分会专门讨论ECMAScript的实现。将会介绍环境记录项(与我们在此讨论的环境环境理论对应),以及它的两种不同类型:声明式环境记录项和对象式环境记录项,可以看到哪种环境记录项会拥有ES5中的执行上下文,了解它的不同部分与不同函数—函数声明和函数表达式的关联。
这一部分主要内容:

  • 环境的概念与作用域的概念有关。
  • 理论中有两种作用域类型:动态和静态静态(词法)作用域
  • ECMAScript使用静态(词法)作用域
  • 然而,with和eval语法可以为静态作用域带来动态性。
  • 诸如作用域,环境,激活对象,激活记录,栈帧,环境帧,环境记录甚至执行上下文等概念—都是用于讨论的近义词(译注:为什么会用这些词,原文以及译注中都有提及)。因此,从ECMAScript的技术角度来说,它们中的一些是另一个的一部分—例如,一个环境记录项是词法环境的一部分,而词法环境又是执行环境的一部分。然而,从逻辑上来收,它们这些抽象定义几乎可以互换使用。因此下面哪一个都是正常的称呼:“全局作用域”,“全局环境”,“全局上下文”,等等。
  • ECMAScript使用链式环境框架模型,在ES3中被称为作用域链。在ES5中环境框架被叫做环境记录项。
  • 一个环境可以被多个内部环境当做它们的外层环境。
  • 词法环境被用来实现闭包,以及解决了函数式参数的问题。
  • ECMAScript中所有函数都是一类函数

后记

  这部分是译者我自己加的。
  本文的理论内容很多,受篇幅限制,原文以及本译文尽可能多的阐释清楚里面的理论。原文的很多理论内容都可以在《计算机程序的构造和解释SICP》中找到对应的理论详解,如果需要继续了解或者加深理解,主要参考SICP的3.2节内容即可。
##延伸阅读:
Structure and Interpretation of Computer Programs (SICP)
3.2 The Environment Model of Evaluation

其他有用的理论:

Scope
Name binding
Call stack
Activation record
Funarg problem
Free variable

引用

英文原版链接 Lexical environments: Common Theory.

词法环境理论:文章列表

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值