您的位置:首页 > 其它

ECMA-262-5 词法环境:通用理论(一)--- 作用域

2017-12-05 17:03 363 查看

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

译者按:本文翻译自Dmitry Soshnikov的关于ECMAScript 系列的文章。其中涉及理论较多,如果想要更好的明白里面所讲的,要对ECMAScript 中相关概念本身有一点的基础,对于之前从未了解过得,推荐先到Dmitry Soshnikov文章中看完ES3系列的介绍,里面也有中文版本。此外,由于理论较多,难免会存在语言的上的生涩或者纰漏,希望指正。,这个系列分成了八个小篇章,可能需要读完才能更好的理解里面讲述的东西。最后,这是本人独自翻译,如果转载注明出处。

介绍

在本文中,将带大家了解词法环境的概念。什么是词法环境?总的来讲,它是某些语言中用来管理静态作用域的一种机制。

本文分为两部分,第一部分将介绍词法环境相关理论,通过讲述多种语言的实现来了解这一概念,其中也会穿插和ECMAScript的对比介绍。

第二部分将会完整的介绍ECMAScript 中词法环境的概念和实现。也包括其他相关概念,比如环境记录项,执行上下文,变量环境等概念。希望通过本文的介绍,能够清楚的理解ECMAScript 中的词法环境。

  在第一部分,我们将会介绍静态作用域,同时为了方便理解,会简单的介绍下动态作用域(我们知道,在ECMAScript中并不采用这种类型的作用域机制)。通过介绍,我们可以了解到词法环境是如何管理代码中错综复杂的词法(作用域)嵌套结构以及它和闭包的密不可分的关系。

  词法环境是一个用于定义特定变量和函数标识符在 ECMAScript 代码的词法嵌套结构上关联关系的规范类型。它是在ECMAScrpt-262-5版的规范中被提进而加入到ECMAScript 的理论体系中。但就这个概念本身来讲,词法环境也被很多其他语言所采用,它是作为独立的一部分加入到ECMAScript 规范中。

  实际上,词法环境对于ECMAScript 来说并非陌生,早在ES3版本的规范中就有了相关的内容,不过在当时我们是用如变量对象,激活对象以及作用域链等术语来称呼这部分内容的。

   严格来说,词法环境相比ES3中的版本,从理论角度来看这个概念更加合适,并且抽象程度更高。(具体原因在后面也会提到)所以,随着ES5版本的诞生,在遇到解释和讨论ECMAScript相关概念的时候,推荐使用新的定义。除此之外,还有一些抽象程度稍低些的概念,比如调用栈(ES3的执行上下文)的一项激活记录(ES3的激活对象),也是属于新的定义的一部分。

   这一章主要介绍环境和一些程序语言理论programming languages theory (PLT)的知识。我们将从几个角度去了解在不同语言中的环境概念的实现,从而明白为什么需要词法环境以及清楚词法环境的结构是如何构建的。事实上,当你弄明白这些,那么再去看ES中的词法环境,那些概念将会变得清晰易懂。

通用理论

  下面涉及的所有概念,包括激活对象AO,作用域链及词法环境等,都离不开一个共同的理论基础,作用域。所以,首先我们来看看作用域概念本身和它的作用域类型。

作用域

  一般来说,作用域是用来管理程序不同部分中的变量声明或函数声明的可见性和生命周期(即对某一变量和方法具有访问权限的代码空间)

  有很多技术思想都与作用域有着密不可分的联系。比如命名空间(命名空间是一种封装事物的方法,是一个抽象的概念)和模块化,它们能够实现系统模块化和解决命名冲突。同样的,还有函数的局部变量以及块级作用域。这些技术能够提高程序的抽象化和封装内部数据,这样一来,我们就不用关心内部的实现细节和担心变量名冲突。

  作用域让我们能够在一个程序中使用多个具有相同命名的变量,但可能它们每个都有不同的含义和值。从这角度来看:

作用域是将一个变量与值关联的封闭上下文。

  或者换个角度,我们可以说作用域是一个变量或者表达式有其意义的逻辑边界。最直观的表现就是全局变量和局部变量,通过这些变量分类,可以反映出一个变量在它生命周期中所处的逻辑边界。

  块级作用域,抑或是函数的概念,体现了作用域的一个主要特性—作用域是嵌套结构。(被包含或者包含其他作用域)但并非所有语言的实现都能完整支持这一属性,就如我们看到的,有的实现不支持嵌套函数(内部函数),也有的不支持块级作用域。

就块级作用域而言,看下面这段C语言代码:

// global "x"
int x = 10;
void foo() {
// local "x" of "foo" function
int x = 20;
if (true) {
// local "x" of if-block
int x = 30;
printf("%d", x); // 30
}
printf("%d", x); // 20
}

foo();
printf("%d", x); // 10


将上面的过程用一张图表示:



  通过上面的图可以看到,C支持块级作用域。但对于ECMAScript,在ES6版本之前,不支持块级作用域,可以通过下面这段代码了解:

var x = 10;
if (true) {
var x = 20;
console.log(x); // 20
}
console.log(x); // 20


  后来,ES6标准中新加入了let关键字来创建一个块级作用域 :

let x = 10;
if (true) {
let x = 20;
console.log(x); // 20
}
console.log(x); // 10


  不过在let诞生之前,也有实现块级作用域的方法,那就是立即执行函数(IIFE):

var x = 10;
if (true) {
(function (x) {
console.log(x); // 20
})(20);
}
console.log(x); // 10


  作用域的另外一个重要的特性就是用于变量解析。当几个程序员在同一个项目中使用了相同命名的变量名(这是会经常出现的),比如for循环的变量i,程序需要一种机制去确定这些i分别对应的值与含义。从理论上来讲有两种方式,对应着两种作用域,分别是静态作用域与动态作用域,下面将具体了解下这两种作用域。

静态(词法)作用域

  静态作用域又叫做词法作用域。在静态作用域中,一个标识符总是引用自离他最近的词法环境(词法环境的具体表示可以理解为上层变量对象,译者注).。‘词法’一词在这里就是程序上下文的一个属性。一个变量在源代码中所在的词法意义上的位置,就是指这个变量在代码中的实际位置,而采用静态(词法)作用域的变量就叫词法变量。“环境”一词指的是在变量或函数在定义时围着它的词法内容,即实际代码中围着它的最近的代码块或是全局环境:

var x = 10;

function foo() {

var y = 20;

function bar() {
var z = 30;
}

}


  这里foo函数被全局环境围着,bar函数被foo所围着。

  静态表示一个标识符所在的作用域和它的含义在词法分析阶段确定。也就是说,在程序运行前通过程序定义的位置,决定变量的引用和变量声明所在的作用域,这就是静态作用域。

  我们看下面这个例子:

var x = 10;
var y = 20;

function foo() {
console.log(x, y);
}
foo(); // 10, 20
function bar() {
var y = 30;
console.log(x, y); // 10, 30
foo(); // 10, 20
}
bar();


  在这个例子中,词法变量x只在全局作用域中有声明,值为10。所以在下面访问的时候,就会取到全局作用域中的x值。

  对于y,在这段代码中有两处定义,分别处于bar构成的作用域与全局作用域中。上面提到过,一个变量会去引用包含它的最近的词法作用域。实际上,在bar函数中y被定义为30,所以在查找y的时候,当找到bar中有定义,就会停止查找,将y与30绑定,不再向上查找全局中的y声明。

   然而,同样的y通过调用foo得到输出20,而不是30,即使foo是在bar中被调用,并且bar中同样包含y。这说明变量的绑定并不依赖于调用者环境(这里bar是caller,foo是callee),随着调用者环境改变而改变。

  对于静态作用域,去确定一个变量或者函数它所在的词法环境,是在变量或者函数创建的时候,是静态的(不变的),而非运行时动态绑定。所以,foo创建的时候,离它最近的作用域是全局作用域,所以在编译阶段就把全局作用域和foo绑定在一起,直到foo销毁。当运行阶段,foo需要输出y时,就到foo在编译阶段绑定的,离foo最近的全局作用域中去查找y,得到20。换句话说,词法作用域的函数中遇到既不是形参也不是函数内部定义的局部变量的变量时,去函数定义时的环境中查询。

  今天使用静态作用域的语言有:C,Java,ECMAScript, Python, Ruby, Lua等等。

  后面将会介绍词法作用于的实现机制(毕竟这正是ECMAScript所采用的),以及它与一类函数一些特别的地方。下面先来看看另一种作用于机制—动态作用域。将会与它与静态相比较,并且说说为什么不能通过动态作用域实现闭包,此外,我们也可以看到,ECMAScript也有一些动态作用域的特性。

引用

英文原版链接 Lexical environments: Common Theory.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息