一, 编译器, 引擎, 作用域
众所周知, JavaScript 是一门编译语言, 简单的说, 任何 JavaScript 代码片段在执行前都要进行编译
以片段 var a = 2 做一个示例, JavaScript 编译器首先会对 var a = 2 这段程序进行编译, 然后做好执行它的准备, 并且通常马上就会执行它. 首先需要先了解 JavaScript 编译器中的几个重要角色.
引擎: 从头到尾负责整个 JavaScript 程序的编译及执行过程.
编译器: 引擎的好朋友之一, 负责语法分析及代码生成等脏活累活
作用域: 引擎的另一个好朋友, 负责收集并维护由所有声明的标识符 (变量) 组成的一系列查询, 并实施一套非常严格的规则, 确定当前执行的代码对这些标识符的访问权限.
1, 当你看到 var = 2 这段程序时, 编译器会询问作用域是否存在一个该名称的变量存在于同一个作用域的集合中, 如果是, 编译器会忽略该声明, 继续进行编译; 否则他会要求作用域在当前作用域的集合中声明一个新的变量, 并命名为 a
2, 加下来编译器会为殷勤生成运行时所需的代码, 这些代码被用来处理 a=2 这个赋值的才做, 引擎运行时会首先询问作用域, 在当前的作用域集合中是否存在一个叫 a 的变量, 如果是, 引擎就会使用这个变量, 如果否, 引擎会继续查找该变量(查找规则后面文章会具体描述)
如果引擎最终找到了 a 变量, 就会将 2 赋值给它. 否则引擎就会抛出一个异常.
总结: 变量的赋值操作会执行两个动作, 首先编译器会在当前作用域中声明一个变量(如果之前没声明过)
, 然后再运行时引擎会在作用域中查找该变量, 如果能找到就会对它赋值.
引擎查找变量的规则, 分为两种, LHS 查询和 RHS 查询. 查找规则不同, 会影响最终的查找结果.
LHR: 当变量出现在赋值操作的左侧时进行 LHS 查询,
- // LHR
- // 找到变量的容器, 从而对其进行赋值
- a = 2
RHS: 非左侧时, 即 RHS, 意为'得到 xx 的值', 进行 RHS 查询
- // RHS
- // 没有对 a 进行赋值, 需要查找并取得 a 的值, 这样才能将值传递给 console.log()
- console.log(a)
- eg:
- function foo(a){
- console.log(a) // 2
- }
- foo(2)
引擎: 我说作用域, 我需要为 foo 进行 RHS 引用. 你见过它吗?
作用域: 别说, 我还真见过, 编译器那小子刚刚声明了它. 它是一个函数, 给你.
引擎: 哥们太够意思了! 好吧, 我来执行一下 foo.
引擎: 作用域, 还有个事儿. 我需要为 a 进行 LHS 引用, 这个你见过吗?
作用域: 这个也见过, 编译器最近把它声名为 foo 的一个形式参数了, 拿去吧.
引擎: 大恩不言谢, 你总是这么棒. 现在我要把 2 赋值给 a.
引擎: 哥们, 不好意思又来打扰你. 我要为 console 进行 RHS 引用, 你见过它吗?
作用域: 咱俩谁跟谁啊, 再说我就是干这个. 这个我也有, console 是个内置对象. 给你.
引擎: 么么哒. 我得看看这里面是不是有 log(..). 太好了, 找到了, 是一个函数.
引擎: 哥们, 能帮我再找一下对 a 的 RHS 引用吗? 虽然我记得它, 但想再确认一次.
作用域: 放心吧, 这个变量没有变动过, 拿走, 不谢.
引擎: 真棒. 我来把 a 的值, 也就是 2, 传递进 log(..).
二, 作用域嵌套
作用域是根据名称查找变量的一套规则.
当一个块或函数嵌套在另一个块或函数中时, 就发生了作用域的嵌套. 因此, 在当前作用域中无法找到某个变量时, 引擎就会在外层嵌套的作用域中继续查找, 直到找到该变量, 或抵达最外层的作用域 (也就是全局作用域) 为止.
- function foo(a){
- console.log(a+b)
- }
- var b = 2
- foo(2) // 4
回顾一下前面的作用域和引擎之间的对话.
引擎: foo 的作用域兄弟, 你见过 b 吗? 我需要对它进行 RHS 引用.
作用域: 听都没听过, 走开.
引擎: foo 的上级作用域兄弟, 咦? 有眼不识泰山, 原来你是全局作用域大哥, 太好了. 你见过 b 吗? 我需要对它进行 RHS 引用.
作用域: 当然了, 给你吧.
遍历嵌套作用域链的规则很简单: 引擎从当前的执行作用域开始查找变量, 如果找不到, 就向上一级继续查找. 当抵达最外层的全局作用域时, 无论找到还是没找到, 查找过程都会停止.
可以把作用域链比喻成一个建筑
image.PNG
这个建筑代表程序中的嵌套作用域链. 第一层楼代表当前的执行作用域, 也就是你所处的位置. 建筑的顶层代表全局作用域. LHS 和 RHS 引用都会在当前楼层进行查找, 如果没有找到, 就会坐电梯前往上一层楼, 如果还是没有找到就继续向上, 以此类推. 一旦抵达顶层(全局作用域), 可能找到了你所需的变量, 也可能没找到, 但无论如何查找过程都将停止.
三, 两种查询的区别?
在变量还没有声明的时候, 这两种查询的行为是不一致的.
- function foo(){
- a = 0
- console.log(b)
- }
- foo()
在上述代码中, 对于 a 是进行的 LHS 查询, 对于 b 是进行的 RHS 查询.
如果 LHS 查询在全局作用域中也无法找到目标变量, 全局作用域中就会热心的创建一个具有该名称的变量(非严格模式下会热心创建, 严格模式下禁止自动或隐式的创建全局变量).
如果 RHS 查询在全局作用域中搜寻不到所需的变量, 引擎就会抛出一个 ReferenceError 异常.
如果你对 RHS 查询到的变量进行了不合理的操作, 比如对一个非函数类型的值进行函数调用等, 那么引擎会抛出 TypeError 的异常,
ReferenceError 同作用域判别失败相关, 而 TypeError 则代表作用域判别成功了, 但是对结果的操作是非法或不合理的.
老铁, 觉得有用点赞.
参考资料:
《你不知道的 JavaScript》
来源: http://www.jianshu.com/p/aff761ea046f