一、作用域是什么
 作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。
 为了更直观的理解,可以想象一下,下面这个建筑代表程序中的嵌套作用域链。第一层楼代表当前的执行作用域,也就是你所处的位置。建筑的顶层代表全局作用域。
 
 如果查找的目的是对变量进行赋值(set),那么就会使用 LHS 查询;如果目的是获取变量的值(get),就会使用 RHS 查询。赋值操作符会导致 LHS 查询。=操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作。
 LHS 和 RHS 引用都会在当前楼层进行查找,如果没有找到,就会坐电梯前往上一层楼, 如果还是没有找到就继续向上,以此类推。一旦抵达顶层(全局作用域),可能找到了你 所需的变量,也可能没找到,但无论如何查找过程都将停止。
 与其很官方的解释LHS查询,RHS查询,不如来个实例,一起来找找下面栗子LHS查询,RHS查询分别有几处?
 栗子1
 function foo(a){     var b = a;     return a + b; } var c = foo(2); 
 我们来分析一下
 首先,var c在其作用域中声明新变量。这会在最开始的阶段,也就是代码执行前进行。
 接下来,c=foo(2)会 LHS查询 变量c并对其进行赋值。
 foo(..) 函数的调用需要对 foo 进行 RHS 引用,意味着“去找到 foo的值,并把 它给我”。并且 (..) 意味着 foo 的值需要被执行,因此它最好真的是一个函数类型的值!
 代码中隐式的a=2操作可能很容易被你忽略掉。这个操作发生在 2被当作参数传递给foo(..) 函数时,2 会被分配给参数  a。为了给参数 a(隐式地)分配值,需要进行一次 LHS 查询。
 同样,var b在其作用域中声明新变量,接着b=a会查询(LHS查询)变量b并对其进行赋值
 这里还有对a进行的 RHS 引用,并且将得到的值传给了b
 最后,对a和b进行的 RHS 引用,并且将得到的值传给了c
 因此,这里有3处LHS查询
 c = ..;、a = 2(隐式变量分配)、b = .. 
 4处RHS查询
 foo(2..、= a;、a ..、.. b 
 二、词法作用域
 简单地说,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域 不变(大部分情况下是这样的)。
 说到这,如果你还有疑问,看看下面的栗子,哩就秒懂了
 栗子 2
  上面的代码片段有三个作用域气泡,分别是
 上面的代码片段有三个作用域气泡,分别是
 作用域气泡 1 包含着整个全局作用域,其中只有一个标识符:foo。
 作用域气泡 2 包含着 foo 所创建的作用域,其中有三个标识符:a、bar 和 b。
 作用域气泡 3 包含着 bar 所创建的作用域,其中只有一个标识符:c。
 作用域气泡由其对应的作用域块代码写在哪里决定,它们是逐级包含的
 注意一个点: 作用域查找 会在找到第一个匹配的标识符时停止
 在多层的嵌套作用域中可以定义同名的 标识符,这叫作“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)。抛开遮蔽效应, 作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见 第一个匹配的标识符为止。
 三、函数作用域
 函数是 JavaScript 中最常见的作用域单元。本质上,声明在一个函数内部的变量或函数会在所处的作用域中“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。
 我们再次分析一下 栗子2,它并不理想,因为它导致一些额外的问题:
 首先必须声明一个具名函数 foo(),这意味着 foo 这个名称本身“污染”了所在作用域(全局作用域)。其次,必须显式地通过函数名(foo())调用这个函数才能运行其中的代码。
 我们理想的情况,函数不需要函数名(或者至少函数名可以不污染所在作用域),并且能够自动运行,很幸运,JavaScript这两点都能实现
 立即执行函数表达式
 var a = 2; (function IIFE( global ) {     var a = 3;     console.log( a ); // 3      console.log( global.a ); // 2 })( window );     console.log( a ); // 2 
 由于函数被包含在一对( )括号内部,因此成为了一个表达式,通过在末尾加上另外一个 ( )可以立即执行这个函数,比如(functionfoo(){..})()。第一个( )将函数变成表 达式,第二个( )执行了这个函数
 四、块作用域
 尽管函数作用域是最常见的作用域单元,当然也是现行大多数 JavaScript 中最普遍的设计 方法,但其他类型的作用域单元也是存在的,并且通过使用其他类型的作用域单元甚至可 以实现维护起来更加优秀、简洁的代码。
 在 ES6 中引入了 let 关键字(var 关键字的表亲),用来在任意代码块中声明变量。通过代码来解释
 for (let i=0; i<10; i++) {      console.log( i ); } console.log( i ); // ReferenceError 
 for 循环头部的 let 不仅将 i 绑定到了 for 循环的块中,事实上它将其重新绑定到了循环 的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。 下面通过另一种方式来说明每次迭代时进行重新绑定的行为:
 { 
     let j;     for (j=0; j<10; j++) {         let i = j; // 每个迭代重新绑定!         console.log( i );     } 
 } 
 读到这里,希望亲可以翻看一下近期写的代码,通过本文章的总结结合自己的理解再捋一捋作用域