JavaScript作用域(一)

103 阅读5分钟

JavaScript实际上是一门 编译语言

1.1 传统编译语言的编译原理

1.分词/词法分析

将字符串分解成对编程语言来说有意义的代码块,这些代码块被称为词法单元(token)。

如var a = 2;。通常会被分解成 为下面这些词法单元:var、a、=、2 、;。

2.解析/语法分析

将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树——“抽象语法树”(Abstract Syntax Tree,AST)。    

3.代码生成

将AST转换为可执行代码的过程。

JavaScript 引擎要复杂得多,JavaScript 的编译过程不是发生在构建之前的。大部分情况下编译发生在代码执行前的几微秒(甚至更短!)的时间内,以此来保证性能最佳。

1.2 理解作用域

1.2.1 不同角色

1、引擎

从头到尾负责整个 JavaScript 程序的编译及执行过程。

2、编译器

负责语法分析及代码生成等脏活累活。

3、作用域

负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。


1.2.2 对话

var a = 2;

对引擎来说,这里有两个完全不同的声明,一个由编译器在编译时处理(声明),另一个则由引擎在运行时处理(赋值)。

  1. 编译器首先会将这段程序分解成词法单元,然后解析成一个树结构,接着开始进行代码的生成。
  2. 在代码生成过程中,遇到 var a,编译器会询问作用域,在同一个作用域集合中是否有同名变量。有则忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a。 --- 声明(在代码执行前)
  3. 编译器会为引擎生成运行时所需的代码,这些代码被用来处理 a = 2 这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作 a 的变量。如果存在引擎则使用这个变量;如果否,引擎会继续查找该变量。
  4. 如果引擎最终找到了 a 变量,就会将 2 赋值给它。否则引擎就会抛出一个异常!

总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

1.2.3 LHS和RHS

1、RHS

查询某个变量的值、谁是赋值操作的源头

RHS 在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError 异常。

如果 RHS 查询找到了一个变量,但尝试对这个变量的值进行不合理的操作, 比如试图对一个非函数类型的值进行函数调用,或着引用 null 或 undefined 类型的值中的属性,那么引擎会抛出TypeError。

ReferenceError 同作用域判别失败相关,而 TypeError 则代表作用域判别成功了,但是对结果的操作是非法或不合理的。

2、LHS

试图找到变量的容器本身、赋值操作的目标是谁

LHS 查询时,如果在顶层(全局作用域)中也无法找到目标变量。非 “严格模式”下,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在。 严格模式中也会抛出ReferenceError 异常。

注意: function foo(a){} 不是变量声明和赋值。 不能理解为var foo、foo = function(a) {}。这里不需要进行 LHS 查询。 因为编译器可以在代码生成的同时处理声明和值 的定义,比如在引擎执行代码时,并不会有线程专门用来将一个函数值“分 配给”foo。因此,将函数声明理解成前面讨论的 LHS 查询和赋值的形式并 不合适。

小测验:

function foo(a) { 
    var b = a;
    return a + b;
}
var c = foo( 2 );

1. 找到其中所有的LHS查询。(这里有3处!)
2. 找到其中所有的RHS查询。(这里有4处!)

2 词法作用域

2.1 作用域工作模型

作用域共有两种主要的工作模型。

第一种是最为普遍的,被大多数编程语言所采用的词法作用域,JS采用的是这种。

另外一种叫作动态作用域,仍有一些编程语 言在使用(比如 Bash 脚本、Perl 中的一些模式等)。

2.2 词法作用域

词法作用域就是定义在词法阶段的作用域。

由你在写代码时将变量和块作用域写在哪里来决定的。

1、作用域是嵌套的,不会交叉!

2、作用域查找会在找到第一个匹配的标识符时停止。

3、全局变量会自动成为全局对象(比如浏览器中的 window 对象)的属性,通过window.a可以访问被同名变量所遮蔽的全局变量。但非全局的变量如果被遮蔽了,无论如何都无法被访问到。

4、无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。

5、词法作用域查找只会查找一级标识符。如果代码中引用了 foo.bar.baz, 词法作用域查找只会试图查找foo标识符,找到这个变量后,对象属性访问规则会分别接 管对 bar 和 baz 属性的访问。

2.3 欺骗词法(不推荐甚至禁止使用)

即在运行中修改作用域,会导致性能下降。

严格模式下,with被禁,eval功能被限制

1、eval

eval("var b = 3;")
非严格模式下,b和eval同级,严格模式下,同级不能访问。

2、with

相关资料

《你不知道的JavaScript 上卷》