首页 > 代码库 > 【读书笔记】你不知道的JavaScript(上卷)--作用域是什么
【读书笔记】你不知道的JavaScript(上卷)--作用域是什么
第一章 作用域
1、理解作用域
几个名词的介绍
引擎:从头到尾负责整个JavaScript程序的编译及执行过程
编译器:负责语法分析及代码生成器等脏活累活
作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
对var a = 2;进行分解,了解引擎等是如何工作。
编译器首先将这段程序分解成词法单元,然后将词法单元解析成一个树结构。但是当编译器开始进行代码生成时,它对这段程序的处理方式会和预期的有所不同。
编译器会进行如下处理
1)遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一作用域的集合中。如果是,编译器会忽略该声明,继续2进行编译;否则它会要求作用域在当前作用域的集合中声明一个新变量,并命名为a。
2)接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理a = 2这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作a的变量。如果是,引擎就会使用这个变量;如果否,引擎就会继续查找该变量。如果引擎最终找到了a变量,就会将2赋值给它。否则引擎就会抛出异常。
总结:变量的赋值操作会执行两个动作,首先编译器会编译当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对他赋值。
2、编译器的LHS和RHS
变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询
考虑以下代码:
console.log(a);
其中对a的引用是一个RHS引用(因为这里是将a的值传递给console.log(..))。
相比之下,例如:
a = 2;
这里对a的引用则是LHS引用,将2的值赋给a。
总结:LHS和RHS的含义是 "赋值操作的左侧或右侧" 并不一定意味着就是 "=赋值操作符的左侧或右侧" 。赋值操作还有其他几种形式,因此在概念上最好将其理解为 "赋值操作的目标是谁(LHS)" 以及 "谁是赋值操作的源头(RHS)" 。
考虑下面程序,其中既有LHS也有RHS引用:
function foo () { console.log(a); //2 } foo(2);
最后一行 foo(..) 函数的调用需要对foo进行RHS引用,意味着 "去找到foo的值,并把它给我" 。并且(..)意味着foo的值需要被执行,因此它最好真的是一个函数类型的值!
代码中隐式的a = 2操作可能很容易被忽略,这里进行了一次LHS查询。
小测试:
function foo(a) { var b = a; return a + b; } var c = foo(2);
1)、找到所有的LHS查询
c = ..;、a = 2(隐式变量分配)、b = ..
2)、找到所有的RHS查询
foo(2..、=a;、a..、..b
3、作用域嵌套
当一个块或函数嵌套在另外一个块或函数时,就会发生了作用域的嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。
4、异常
在变量还没有声明(在任何作用域中都无法找到该变量)的情况下,LHS和RHS两种查询的行为是不一样的。
考虑如下代码:
function foo(a) { console.log(a + b); b = a; } foo(2);
第一次对b进行RHS查询时无法找到该变量的。也就是说,这是一个 "未声明" 的变量,因为在任何相关的作用域中都无法找到它。
如果RHS查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出ReferenceError异常。值得注意的是,ReferenceError是非常重要的异常类型。
相比较之下,当引擎执行LHS查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非 "严格模式" 下。
接下来,如果RHS查询找到了一个变量,但是你尝试对这个变量进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或者引用null或undefined类型的值中的属性,那么引擎会抛出另外一个类型异常,叫作TypeError。
ReferenceError同作用域判别失败相关,而TypeError则代表作用域判别成功了,但是对结果操作是非法或不合理的。
【读书笔记】你不知道的JavaScript(上卷)--作用域是什么