javascript闭包

146 阅读2分钟

闭包的定义:闭包是由函数和函数能够访问的自由变量所组成,即使创建它的上下文已经被销毁,它依然存在。

  1. 最经典的例子:
for (var i = 0; i < 10; i ++) {
    setTimeout(function () {
        console.log(i)
    }, i * 1000)
}
  • 毫无疑问,我们都知道这里将会每隔1s输出一个10,为什么呢?我们可以利用Event Loop来解释这一现象。当程序开始执行的时候,同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。在这里,for会进入主程序开始执行,而setTimeout进入Event Table ,并注册回调函数。由于js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。而此时,for循环已经执行完毕,此时的i成为全局变量,Event Queue中的函数访问到的变量i均为10

解决方法有很多,比如使用IIFE,或者ES6中的let来创建块级作用域。这里列出一个通过js值类型的方法来决定这个问题:

function func (i) {
    setTimeout(function() {
        console.log(i)
    }, i * 1000)
}
for (var i = 0; i < 10; i ++) {
    func(i)
}

最后,我们来分析一下下面这道面试必刷题

var arr = []
for (var i = 0 ; i < 5; i ++) {
    arr[i] = (function(j) {
        return function() {
            console.log(j)
        }
    })(i)
}
arr[0]() // 0
arr[1]() // 1
arr[2]() // 2
...
  • 这里我们是通过IIFE来实现往数组中添加值的,数组的每一项值都是一个函数,调用函数,打印出循环变量 i
  • 当执行到 arr[0]() 时,函数 arr[0]的作用域链表示如下 :
    // AO 表示活动对象, VO表示变量对象,其实它俩是等价的
    函数上下文 [AO, anonymous(匿名函数)context.VO,Global(全局)context.VO]
  • 执行函数arr[0]()的时候,首先函数会到自己的AO中寻找是否存在变量 i,没有找到就会去匿名函数中的VO寻找变量 i (i = 0),此时,通过闭包,我们将每次循环的变量i 都存在其上下文中,能够访问到,便输出 i,并不再向GlobalContext中的VO( i 的值为 5)寻找了。