对于几乎全部编程语言,最基本的功能之一就是可以存储变量的值,而且能在以后对这个值进行访问和修改。这样就会带来几个问题,这些变量存储在哪里?程序在须要的时候又是如何找到它们的?要解决这些问题,就须要引入一套规则来存储变量和访问变量,这套规则就是做用域。javascript
在刚开始接触 JavaScript 这门语言时,确定会常常接触到 JavaScript 是动态语言, 是解释执行的,但事实上 JavaScript 是一门编译语言。只不过和 Java、C# 这些传统意义上的编译语言不一样,JavaScript 的编译过程不是发生在构建以前的。大部分状况下,JavaScript 的编译过程发生在代码执行前的很短期内。也就是说 JavaScript 代码在执行前都要进行编译。java
为了更好地理解做用域,咱们须要明确下面几个概念编程
从头至尾负责整个 JavaScript 程序的编译及执行过程微信
负责语法分析及代码生成等脏活累活编程语言
负责收集并维护由全部声明的标识符(变量) 组成的一系列查询, 并实施一套很是严格的规则, 肯定当前执行的代码对这些标识符的访问权限。函数
下面咱们从引擎、编译器和做用域的角度,分析 var a = 2
这条声明语句,看看它们是如何协同完成工做的post
简单来讲,变量的赋值操做会执行两个动做, 首先编译器会在当前做用域中声明一个变量(若是以前没有声明过), 而后在运行时引擎会在做用域中查找该变量, 若是可以找到就会对它赋值,不然就会并抛出一个异常。学习
咱们知道引擎查找变量的过程在做用域中进行的,而这个过程一般会涉及多个做用域。spa
当一个块或函数嵌套在另外一个块或函数中时, 就发生了做用域的嵌套。 所以, 在当前做用域中没法找到某个变量时, 引擎就会在外层嵌套的做用域中继续查找, 直到找到该变量,或抵达最外层的做用域(也就是全局做用域) 为止。code
为了便于理解,能够将做用域嵌套比喻成一栋高楼,咱们从一楼(当前做用域)开始查找,若是没有找到,就会前往上一个楼层继续查找,以此类推。一旦到达顶层(全局做用域),可能找到,也可能没有找到,查找过程都必须中止。
继续上文的示例,引擎在执行编译器生成的代码时,会经过查找变量 a 来判断它是否已经声明过。查找的过程由做用域进行协助, 可是引擎执行怎样的查找, 会影响最终的查找结果。查找过程分为两类:LHS查询和RHS查询。其实很简单,当变量出如今赋值操做的左侧时进行 LHS 查询, 出如今右侧时进行 RHS 查询。
更准确一点的讲, RHS 查询是查找某个变量的值,而 LHS 查询是查找变量的容器自己,从而能够对其赋值。以下面的示例:
var a = 2; // a: LHS查询 var b = 3; // b: LHS查询 a = b; //a: LHS查询 b:RHS查询
为何区分 LHS 和 RHS 是一件重要的事情?
由于在变量尚未声明(在任何做用域中都没法找到该变量) 的状况下, LHS 和 RHS两种查询的行为是不同的。
1.当引擎执行 RHS 查询时,若是 RHS 查询在全部嵌套的做用域中遍寻不到所需的变量, 引擎就会抛出 ReferenceError异常。
console.log(a); //ReferenceError: a is not defined
2.当引擎执行 LHS 查询时, 若是在顶层(全局做用域) 中也没法找到目标变量,全局做用域中就会建立一个具备该名称的变量, 并将其返还给引擎, 前提是程序运行在非“严格模式” 下。若是在“严格模式”下,引擎也会抛出 ReferenceError异常。
//非严格模式 var a =2; b = a; console.log(b); //2
//严格模式 'use strict'; var a =2; b = a; console.log(b); //ReferenceError: b is not defined
另外,若是 RHS 查询找到了一个变量, 可是你尝试对这个变量的值进行不合理的操做,好比试图对一个非函数类型的值进行函数调用, 或着引用 null 或 undefined 类型的值中的属性, 那么引擎会抛出另一种类型的异常, 叫做TypeError。
ReferenceError 表明做用域判别失败相关, 而 TypeError 则表明做用域判别成功了, 可是对结果的操做是非法或不合理的。
《你不知道的JavaScript》