作用域
何为作用域,这种问题其实很难受,绝大部分人对于作用域没有太多的概念,更多的是对于局部作用域和全局作用域的区分。对于作用域可以这么理解,它是一套规则,这个规则用于确定在特定场景下如何查找变量,或者说确定当前执行的代码对这些标识符(变量)的访问权限
变量查询
var a=2
首先针对这段代码,由于var关键字的特性,编译器可以拆分为以下代码
var a
a=2
可以合理地假设编译器所产生的代码能够用下面的伪代码进行概括:“为一个变量分配内 存,将其命名为 a,然后将值 2 保存进这个变量。”然而,这并不完全正确。
事实上编译器会进行如下处理。
遇到 var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的 集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作 用域的集合中声明一个新的变量,并命名为 a。
接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理 a = 2 这个赋值 操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作 a 的 变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量。
如果引擎最终找到了 a 变量,就会将 2 赋值给它。否则引擎就会举手示意并抛出一个异 常!
那么引擎是如何查找这个变量
在刚才的例子中,引擎会为变量 a 进行 LHS 查询。另外一个查找的类型叫作 RHS。何为“L”“R”,它们分别代表了左侧与右侧。什么东西的左侧和右侧?是一个赋值操作的左侧和右侧。
换句话说,当变量出现在赋值操作的左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。
讲得更准确一点,RHS 查询与简单地查找某个变量的值别无二致,而 LHS 查询则是试图 找到变量的容器本身,从而可以对其赋值。
console.log(a)
其中对 a 的引用是一个 RHS 引用,因为这里 a 并没有赋予任何值。相应地,需要查找并取 得 a 的值,这样才能将值传递给 console.log(..)你可以将 RHS 理解成取到它的源值,非赋值操作,“得到某某的值”。
LHS 和 RHS 的含义是“赋值操作的左侧或右侧”并不一定意味着就是“= 赋值操作符的左侧或右侧”。赋值操作还有其他几种形式,因此在概念上最 好将其理解为“赋值操作的目标是谁(LHS)”以及“谁是赋值操作的源头 (RHS)”。
function foo(a){
console.log(a)
}
foo(2)
来看看此处有几处查询操作!
foo进行RHS查询,针对形参a传入实参后进行了LHS查询(a=2),对于全局对象console进行了RHS查询,最后对于log函数内的a再次进行RHS查询
作用域嵌套
作用域是根据名称查找变量的一套规则。实际情况中,通常需要同时顾及几个 作用域。
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,在当前作用 域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量, 或抵达最外层的作用域(也就是全局作用域)为止
function foo(a){
console.log(a+b)
}
var b=2
foo(2)
对 b 进行的 RHS 引用无法在函数 foo 内部完成,但可以在上一级作用域(在这个例子中就 是全局作用域)中完成。
可以将它想象成如下对话:
引擎:foo 的作用域兄弟,你见过 b 吗?我需要对它进行 RHS 引用。
foo作用域:听都没听过,走开。
引擎:foo 的上级作用域兄弟,咦?有眼不识泰山,原来你是全局作用域大哥,太好了。你见过 b 吗?我需要对它进行 RHS 引用。
全局作用域:当然了,拿去拿去。
遍历嵌套作用域链的规则很简单:引擎从当前的执行作用域开始查找变量,如果找不到, 就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都 会停止。
为什么区分 LHS 和 RHS 是一件重要的事情?
因为在变量还没有声明(在任何作用域中都无法找到该变量)的情况下,这两种查询的行 为是不一样的。
function foo(a){
console.log(a+b)
b=a
}
foo(2)
很明显此处代码会报错,第一次对 b 进行 RHS 查询时是无法找到该变量的。也就是说,这是一个“未声明”的变 量,因为在任何相关的作用域中都无法找到它。
注意此处引擎抛出的异常,重点不是 b is not defined,而是ReferenceError异常,ReferenceError 是非常重要的异常类型。
相较之下,当引擎执行 LHS 查询时,如果在顶层(全局作用域)中也无法找到目标变量, 全局作用域中就会创建一个具有该名称的变量(这个深有体会吧),并将其返还给引擎,前提是程序运行在非 “严格模式”下。因此,在 严格模式中 LHS 查询失败时,并不会创建并返回一个全局变量,引擎会抛出同 RHS 查询 失败时类似的 ReferenceError 异常。
接下来,如果 RHS 查询找到了一个变量,但是你尝试对这个变量的值进行不合理的操作, 比如试图对一个非函数类型的值进行函数调用,或着引用 null 或 undefined 类型的值中的 属性,那么引擎会抛出另外一种类型的异常,叫作 TypeError。
ReferenceError 同作用域判别失败相关,而 TypeError 则代表作用域判别成功了,但是对 结果的操作是非法或不合理的。
console.log(a);
// ReferenceError
foo()
var foo = function () {
// ...
}
var a;
console.log(a.age);
//TypeError