1、理解作用域

在理解作用域的前提下,我们需要知道JavaScript的工作原理,首先编译器,引擎在处理我们声明的变量时,

例如:

var a = 0

代码在执行的时候会创建变量对象的一个作用域链,这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。

大概理解为:

第一步,编译器会访问作用域是否已经有改名称的变量存在于同一个作用域的集合中,存在,编译器忽略,继续执行编译。不存在会声明一个新的变量,并命名为:a

第二步,编译器会为引擎生成运行时所需要的代码,引擎在运行时同样会访问作用域是否存在一个叫作 a 的变量,存在,引起会引用这个变量并赋值  a = 2 ,不存在,会继续寻找直至最终找不到抛出异常!

2、作用域的层级

变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。

全局上下文,是最外层的上下文,在浏览器中,全局上下文就是我们常说的 windows 对象。

var color = 'blur'

function changeColor () {
   let anotherColor = 'red'
   function swapColor () {
      let tempColor = anotherColor
     anotherColor = color 
     color = tempColor
    // 这里可以访问 color、 anotherColor 和 tempColor
   }
  // 这里可以访问 color 和 anotherColor,但访问不到 tempColor
  swapColors()
}
// 这里只能访问 color
changeColor()

在《JavaScript高级程序设计(第四版)》中有这样一段代码,涉及到各个不同层级作用域之间的相互访问,当代码执行时,如果在同级作用域上下文中没有找到定义的变量或者函数,就会往父上下文中的作用域中去搜索,直至最终在全局上下文中的作用域也没有找到才会抛出异常!

那么代码在执行时,引擎执行怎么样的查找?

通常我们的变量或函数出现赋值操作的左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。RHS 查询与简单的查找某个变量的值别无二致,而 LHS 查询是试图找到变量的容器本身,从而进行赋值。

console.log(a)

我们可以理解上面的代码对 a 进行 RHS 引用,因为这里并没有进行赋值,仅仅得到 a  变量的值。

相对而言:

a = 2

上面的代码,就是对 a 进行 LHS 引用,因为不需要知道 a 之前是什么值,只是为了拿到 a 本身的容器并进行赋值。

在《你不知道的JavaScript(上卷)》中,概念上的理解为:

LHS - 赋值操作的目标是谁

RHS - 谁是赋值操作的源头

function foo (a) {
  console.log(a)
}

foo(2)

在上面代码执行中,我们可以简单的分析下编译器、引擎、作用域之间的工作原理以及执行上下文的作用域链:

首先,我们在编译器中声明了函数 foo,并且对函数 foo() 进行了 RHS 引用,引擎在执行时,访问作用域去搜索编译器中已经声明好的 foo 函数,并且执行,在执行过程中,我们对变量 a 做了赋值的操作 foo (2) ,进行了LHS 的引用,并且我们在引用的同时有进行了 RHS 的操作 console.log(a) 得到 a 的值。以上,就是这段代码的执行过程。

3、作用域嵌套

var color = 'blue'

function changeColor () {
  if (color === 'blue') {
     color = 'red'
  } else {
     color = 'green'
  }
}

changeColor()

上述代码中,我们可以明显的看出作用域之间存在相互嵌套的,而最顶层的作用域即浏览器中的 windows 对象,因此在代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。

4、作用域链增强

try/catch 语句的 catch 块,catch 则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明;

with 语句,会向作用域链前端添加指定的对象。