多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] ## 1.1 作用域 几乎所有编程语言最基本的功能之一,就是能储存变量当中的值,并且能在之后对这个值进行访问或修改。事实上,这是这种储存和访问变量的值的能力将**状态**带给了程序。 变量储存在哪?程序需要时如何找到变量?这些问题说明需要一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量。这套规则被成为**作用域**。 ### 1.1.1 编译原理 尽管通常将JavaScript 归类为“动态”或“解释执行”语言,但事实上它是一门**编译语言**。 与传统编译语言流程(词法分析-语法分析-代码生成)不同: * JavaScript引擎复杂得多,如,在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。 * JavaScript 引擎不会有大量的(像其他语言编译器那么多的)时间用来进行优化,因为与其他语言不同,**JavaScript 的编译过程不是发生在构建之前的**。 * 对于JavaScript 来说,大部分情况下编译发生在代码执行前的几微秒(甚至更短)的时间内。 ### 1.1.2 理解作用域 * 引擎:从头到尾负责整个JavaScript 程序的编译及执行过程。 * 编译器:负责语法分析及代码生成等。 * 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。 **1. 一个赋值操作的过程** 变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。 **2. LHS与RHS查询** * RHS 查询与简单地查找某个变量的值别无二致(目的是获取变量的值); * LHS 查询则是试图找到变量的容器本身,从而可以对其赋值。(目的是对变量赋值) ~~~ function foo(a) { // 这里隐式包含了 a = 2 这个赋值,所以对 a 进行了 LHS 查询 var b = a; // 这里对 a 进行了 RHS 查询,找到 a 的值,然后对 b 进行 LHS 查询,把 2 赋值给 b return a + b; // 这里包含了对 a 和 b 进行的 RHS 查询 } var c = foo(2); // 这里首先对 foo 进行 RHS 查询,找到它是一个函数,然后对 c 进行 LHS 查询把 foo 赋值给 c 以上共3LHS,4RHS ~~~ **3. 作用域嵌套** 当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。 ~~~ function foo(a) { console.log( a + b ); } var b = 2; foo( 2 ); // 4 ~~~ 对b 进行的RHS 引用无法在函数foo 内部完成,但可以在上一级作用域(在这个例子中就是全局作用域)中完成。 引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。 **4. 异常** 在变量还没有声明(在任何作用域中都无法找到该变量)的情况下,LHS查询与RHS查询的行为是不一样的。 ~~~ function foo(a) { console.log( a + b ); b = a; } foo( 2 ); ~~~ 第一次对b 进行RHS 查询时是无法找到该变量的。也就是说,这是一个“未声明”的变量,因为在任何相关的作用域中都无法找到它。 **不成功的RHS 引用会导致抛出ReferenceError 异常。不成功的LHS 引用会导致自动隐式地创建一个全局变量(非严格模式下),该变量使用LHS 引用的目标作为标识符,或者抛出ReferenceError 异常(严格模式下)。** 如果RHS 查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用,或着引用null 或undefined 类型的值中的属性,那么引擎会抛出另外一种类型的异常,叫作`TypeError`。 ReferenceError 同作用域判别失败相关,而TypeError 则代表作用域判别成功了,但是对结果的操作是非法或不合理的。