常见的变量作用域就是 静态作用域(词法作用域) 与 动态作用域 。词法作用域注重的是 write-time ,即 编程时的上下文 ,而 动态作用域 则注重的是 run-time ,即 运行时上下文 。词法作用域中我们需要知道一个函数 在什么地方被定义 ,而动态作用域中我们需要关心的是函数 在什么地方被调用
而 javascript 使用的则是词法作用域
1let value = 1
2
3function foo() {
4 console.log(value)
5}
6
7function bar() {
8 let value = 2
9 foo()
10}
11
12bar() // 1在 javascript 解析模式中,当 foo 被调用的时候:
在动态作用域的解析模式中,当 foo 被调用的时候:
在从内层到外层的变量搜索过程中,当前作用域到外层作用域再到更外层作用域直到最外层的全局作用域,整个搜寻轨迹就是 作用域链

20190307092333.png
一种是 rhs 一种是 lhs
假设有这么一段代码:
1console.log(a) // 输出 undefined
2console.log(a2) // 报错 a2 is not defined
3var a = 1上述代码实际上在变量提升的作用下应该是下面这个顺序:
1var a
2console.log(a) // 输出 undefined
3console.log(a2) // 报错 a2 is not defined
4a = 1rhs 也就是 right-hand-sidea = 1 则是赋值操作,也就是 lhs,英文 left-hand-side
20190307093837.png
闭包是啥?闭包就是从函数外部访问函数内部的变量,函数内部的变量可以持续存在的一种实现。
在了解了词法作用域和变量的查询方式之后,我们看看一个简单的闭包的实现逻辑:
1function f() {
2 num = 1 // 里面的变量
3 function add() {
4 num += 1
5 }
6 function log() {
7 console.log(num)
8 }
9 return { add, log } // 我要到外面去了
10}
11
12add = f().add
13log = f().log
14
15log() // 1 我从里面来,我在外面被调用,还是可以获得里面的变量
16add()
17log() // 2为什么外部定义的 add 函数可以访问 f 函数内部的变量呢。正常情况下外部作用域不可访问内部作用域的变量,但我们将内部访问其内部变量的方法“导出”出去,以至于可以从外部直接调用函数内部的方法,这样我们就可以从函数的外部访问函数内部的变量了。
1arr = []
2for (var i = 0; i < 10; i ++) {
3 arr[i] = function() {
4 console.log(i)
5 }
6}
7arr[2]() // 10首先我们知道 for 循环体内的 i 实际上会被定义在全局作用域中
每次循环我们都将 function 推送到一个 arr 中,for 循环执行完毕后,arr 中张这样:

20190307101411.png
随后我们执行代码 arr[2]() 此时 arr[2] 对应的函数 function(){ console.log(i) } 会被触发
函数尝试搜索函数局部作用域中的 i 变量,搜索不到则会继续向外层搜索,i 被定义到了外层,因此会直接采用外层的 i,就是这里的全局作用域中的 i,等到这个时候调用这个函数,i 早已变成 10 了
那么有什么方法能够避免出现这种情况吗?
ES6 之前的解决方案:
了解了闭包我们就知道了闭包内的变量可以持续存在,所以修改代码将 arr 中的每一项改为指向一个闭包:
1arr = []
2for (var i = 0; i < 10; i ++) {
3 arr[i] = (function() { // 这是一个闭包
4 var temp = i // 闭包内部维护一个变量,这个变量可以持续存在
5 return function() {
6 console.log(temp)
7 }
8 })()
9}这样程序就能按照我们的想法运行了

20190307102109.png
ES6 之后的解决方案:
ES6 之后我们就有了块级作用域因此代码可以改为这样:
1arr = []
2for (let i = 0; i < 10; i ++) { // 使用 let
3 arr[i] = function() {
4 console.log(i)
5 }
6}在使用 let 之后,我们每次定义 i 都是通过 let i 的方法定义的,这个时候 i 不再是被定义到全局作用域中了,而是被绑定在了 for 循环的块级作用域中
因为是块级作用域所以对应 i 的 arr 每一项都变成了一个闭包,arr 每一项都在不同的块级作用域中因此不会相互影响

20190307102815.png
参考: