Javascript的词法作用域分析

2013-03-05 09:53邓绪高
电脑知识与技术 2012年36期

邓绪高

摘要:在JavaScript中,一个方法使用的地方和定义的地方有时会大相径庭,在该方法执行时,它不能访问哪些变量?可以访问哪些变量?这个需要怎么判断呢?这就是该文需要分析的问题——词法作用域。

关键词:JavaScript;执行环境;活动对象;词法作用域;闭包

中图分类号:TP271 文献标识码:A 文章编号:1009-3044(2012)36-8663-04

1 JavaScript词法作用域

JavaScript中词法作用域指变量的作用域不是执行时决定而是在定义时决定,也就是说通过静态分析就能确定,词法作用域取决于源码,因此词法作用域也叫做静态作用域。with和eval除外,所以只能说JS的作用域机制非常接近词法作用域。

我们可以很快的写出一个方法,但你可能没有进行过深入的学习和了解到底方法内部是如何执行,执行的细节又是什么。这就需要了解JavaScript引擎的工作方式,才能了解这些细节,下面我们就把JavaScript引擎对一个方法的解析过程进行一个稍微深入一些的介绍

3 JavaScript方法解析过程

JavaScript是一种解释型语言,要开始解释执行,得通过词法分析和语法分析得到语法分析树后才可以。当一个文档流中包含多个script代码段,那么它们的运行顺序是:

1)读入第一个代码段

2)做词法分析和语法分析,有错则报语法错误,并跳转到步骤5

3)对var变量和function定义做“预解析“

4)执行代码段,有错则报错

5)如果还有下一个代码段,则读入下一个代码段,重复步骤2

6)结束

3.1 特殊说明

全局域(window)域下所有JavaScript代码会被自动执行,可以被看成是一个“匿名方法“,而此“匿名方法“内的其它方法则是在被显示调用的时候才被执行。

3.2 关键步骤

上面的过程,我们主要是分成两个阶段

1)解析:就是通过语法分析和预解析构造合法的语法分析树。

2)执行:执行具体的某个function,JavaScript引擎在执行每个函数实例时,都会创建一个执行环境(ExecutionContext)和活动对象(activeObject)。

3.3 关键概念

到这里,我们再更强调以下一些概念,这些概念都会在下面用一个一个的实体来表示,便于大家理解

1)语法分析树(SyntaxTree)可以直观地表示出这段代码的相关信息,具体的实现就是JavaScript引擎创建了一些表,用来记录每个方法内的变量集(variables),方法集(functions)和作用域(scope)等

2)执行环境(ExecutionContext)可理解为一个记录当前执行的方法(外部描述信息)的对象,记录所执行方法的类型,名称,参数和活动对象(activeObject)

3)活动对象(activeObject)可理解为一个记录当前执行的方法(内部执行信息)的对象,记录内部变量集(variables)、内嵌函数集(functions)、实参(arguments)、作用域链(scopeChain)等执行所需信息,其中内部变量集(variables)、内嵌函数集(functions)是直接从第一步建立的语法分析树复制过来的

上面就是关于语法分析树的一个简单表示,正如我们前面分析的,语法分析树主要记录了每个 function中的变量集(variables),方法集(functions)和作用域(scope)。以下是语法分析树关键点:

1)变量集(variables)中,只有变量定义,没有变量值,这时候的变量值全部为“undefined”

2)作用域(scope),根据词法作用域的特点,这个时候每个变量的作用域就已经明确了,而不会随执行时的环境而改变。

3)作用域(scope)建立规则

a) 对于函数声明和匿名函数表达式来说,[scope]就是它创建时的作用域

b) 对于有名字的函数表达式,[scope]顶端是一个新的JavaScript对象(也就是继承了Object.prototype),这个对象有两个属性,第一个是自身的名称,第二个是定义的作用域,第一个函数名称是为了确保函数内部的代码可以无误地访问自己的函数名进行递归。

4.2 执行环境与活动对象

语法分析完成,开始执行代码。我们调用每一个方法的时候,JavaScript引擎都会自动为其建立一个执行环境和一个活动对象,它们和方法实例的生命周期保持一致,为方法执行提供必要的执行支持,针对上面的几个方法,我们这里统一为其建立了活动对象,具体如下:

上面每一个活动对象都存储了相应方法的内部变量集(variables)、内嵌函数集(functions)、形参(parameters)、实参(arguments)等执行所需信息。活动对象关键点如下:

1) 创建活动对象,从语法分析树复制方法的内部变量集(variables)和内嵌函数集(functions)

2) 方法开始执行,活动对象里的内部变量集全部被重置为 undefined

3) 创建形参(parameters)和实参(arguments)对象,同名的实参,形参和变量之间是【引用】关系

4) 执行方法内的赋值语句,这才会对变量集中的变量进行赋值处理

5) 变量查找规则是首先在当前执行环境的activeObject中寻找,没找到,则顺着执行环境中属性scopeChain指向的activeObject中寻找,一直到Global Object(window)

6) 方法执行完成后,内部变量值不会被重置,至于变量什么时候被销毁,请参考下面一条

7) 方法内变量的生存周期取决于方法实例是否存在活动引用,如没有就销毁活动对象

8) 步骤f和g是使闭包能访问到外部变量的根本原因

5 重释经典案例

在案列一中,由于在一个方法中,同名的实参,形参和变量之间是引用关系,也就是说JavaScript引擎的处理是同名变量和形参都引用同一个内存地址,所以在二中的修改arguments才会有影响到局部变量的这种情况出现。

在案例二中,根据JavaScript引擎变量查找规则,在当前执行环境的ActiveObject中寻找,未果,则顺着执行环境中属性ScopeChain指向的ActiveObject中寻找,一直到GlobalObject(window),所以在四中,因为在当前的ActiveObject中找到了有变量i的定义,只是值为“undefined”,所以直接输出“undefined”了

6 总结

以上是我在学习和使用了JavaScript一段时间后,为了更深入的了解JavaScript,也为了更好的把握对JavaScript的应用,从而在对闭包的学习过程中,自己对于词法作用域的一些总结和理解,中间可能有一些地方不同于真实的JavaScript解释引擎,因为我不是站在一个系统设计者的角度而是站在一个刚入门的前端开发人员的角度上去分析这个问题,希望能对JavaScript开发者理解词法作用域带来一些帮助!

参考文献:

[1] 弗兰纳根.JavaScript权威指南[M].6版.北京:机械工业出版社,2012.

[2] Nicholas C,Zakas.JavaScript高级程序设计[M].3版.北京:人民邮电出版社,2012.

[3] Stephen.浅谈JavaScript的闭包和作用域链[EB/OL]. (2010-01-20).http://blog.endlesscode.com/2010/01/20/javascript-closure-scope-chain/447/1865447.shtml.