几乎全部编程语言最基本的功能之一,就是可以储存变量当中的值,而且能在以后对这个值进行访问或修改。事实上,正是这种储存和访问变量的值的能力将状态带给了程序。react
可是将变量引入程序会引发几个颇有意思的问题,也正是咱们将要讨论的:这些变量住在哪里?换句话说,它们储存在哪里?最重要的是,程序须要时如何找到它们?git
这些问题说明须要一套设计良好的规则来存储变量,而且以后能够方便地找到这些变量。这套规则被称为做用域。github
虽然你们常说 JavaScript 为动态语言,但事实上 JavaScript 为一门编译语言。它不会像其余语言同样提早编译,它会在代码执行前进行编译。编程
在传统编译语言的流程中,程序中的源代码在编译过程当中通常会进行三个步骤。bash
!这里不作过多解释编程语言
理解 JavaScript 做用域,要明白做用域在 js 中起到做用以及其 js 之间的关系。首先这里介绍 js 的三个重要角色。函数
引擎post
从头至尾负责整个 JavaScript 程序的编译及执行过程。spa
编译器设计
引擎的好朋友之一,负责语法分析及代码生成等脏活累活(详见前一节的内容)。
做用域
引擎的另外一位好朋友,负责收集并维护由全部声明的标识符(变量)组成的一系列查 询,并实施一套很是严格的规则,肯定当前执行的代码对这些标识符的访问权限。
为了可以彻底理解 JavaScript 的工做原理,你须要开始像引擎(和它的朋友们)同样思考, 从它们的角度提出问题,并从它们的角度回答这些问题。
当咱们运行到 var a = 2 时,这里会被解释为:
遇到 var a,编译器会
询问做用域
是否已经有一个该名称的变量存在于同一个做用域的 集合中。若是是,编译器会忽略该声明,继续进行编译;不然它会要求做用域在当前做 用域的集合中声明一个新的变量,并命名为 a。
接下来编译器
会为引擎生成运行时
所需的代码,这些代码被用来处理 a = 2
这个赋值 操做。引擎运行时会首先询问做用域
,在当前的做用域集合中是否存在一个叫做 a 的 变量。若是是,引擎就会使用这个变量;若是否,引擎会继续查找该变量。
总结
变量的赋值操做会执行两个动做,首先编译器会在当前做用域中声明一个变量(如 果以前没有声明过),而后在运行时引擎会在做用域中查找该变量,若是可以找到就会对 它赋值
/**
* 这里对 a 进行了LHS查询
* 当变量值出如今赋值操做符左侧时会进行LHS
**/
var a = 2
/**
* 这里对 a 进行了RHS查询
* RHS 可译为(取到它的源值,获取某某的值)
**/
console.log(a)
/**
* 这里包含了一个隐藏的LHS
* 当执行demo(...)时,会隐式的执行 a = 2
**/
function demo(a) {
console.log(a)
}
demo(2)
复制代码
function foo(a) {
console.log( a ); // 2
}
foo( 2 );
复制代码
让咱们把上面这段代码的处理过程想象成一段对话,这段对话多是下面这样的。
当一个块或函数嵌套在另外一个块或函数中时,就发生了做用域的嵌套。所以,在当前做用 域中没法找到某个变量时,引擎就会在外层嵌套的做用域中继续查找,直到找到该变量, 或抵达最外层的做用域(也就是全局做用域)为止。
LHS 和 RHS 引用都会在当前做用域进行查找,若是没有找到,就会像冒泡同样前往上一层做用域, 若是仍是没有找到就继续向上,以此类推。一旦抵达顶层(全局做用域),可能找到了你 所需的变量,也可能没找到,但不管如何查找过程都将中止。
function foo(a) {
console.log( a + b );
b = a;
}
foo( 2 );
复制代码
当代码对 b 执行 RHS 查询时 遍历做用域也没法找到,此时会抛出 ReferenceError 异常。 可是当程序在执行 LHS 查询时 遍历做用域一样没法找到,在非严格模式下
全局做用域会建立一个具备更名称的变量。
⚠️ 当 RHS 查找到一个指定变量,可是对该变量执行不合理操做时,会抛出 TypeError 异常。因此 ReferenceError 表明查找失败。TypeError 则查找成功。