闭包以及其ES6下的使用

关于闭包

众所周知,JavaScript的一大特色就是闭包的存在,能够让内部函数访问到外部函数的变量。那么,我们该怎样认识闭包,以及在ES6标准下闭包会是怎样的呢?

从一段代码开始

var test = function () {
  var arr = []
  for(var i = 0; i < 5; i++){
    arr.push(function () {
      return i*i
    })
  }
  return arr
}

var test1 = test()
console.log(test1[0]())
console.log(test1[1]())
console.log(test1[2]())

老生常谈的闭包典型例子,不用说,三个控制台打印的结果均为25(这种明显有坑的题大概面试答案都能猜出来...),让我们来一步一步解析这段代码:

  1. 创建了一个循环
    我们在这个for循环内部,为数组push了5个函数,均为
function () {
  return i*i
}

我们期望这里内部函数i能够获取到每次循环的i值,但是显然没能如我们所料。这是为什么呢,我们暂且不表,继续看下去

  1. 调用了函数test
    我们将test这个函数执行,并赋给test1。这样test1就成为了一个数组,并且是其内部元素均为函数。需要注意,这里内部的函数都还未执行!它们还没有在任何地方被调用!
  2. 打印其中部分值
    在打印这一步能够清楚的看到,我们开始调用其内部的函数。注意调用的时间,是在打印这一步,那么此时的i值为几呢?显然,此时我们已经在循环外部,i值为5。所以这也是为何打印值均为25。

那么,怎样能够得到我们预期的效果呢?或者说,怎样让循环在每次执行的时候,都使得内部函数能够记住外部传入的i值呢?我们开始想到作用域
在JavaScript中,不存在块级作用域(先不讨论ES6),只有函数作用域。作用域的好处在于我们的内部函数是可以访问外部函数的变量的,这一点非常重要!
回头看代码,我们需要让循环中每次push的函数都记住当前循环下的i,那么我们就需要一个立即执行函数,让每次循环直接得出结果,那么我们来改造一下这段代码:


var test = function () {
  var arr = []
  for(var i = 0; i < 5; i++){
    arr.push(function (n) {
      return n*n
    }(i))
  }
  return arr
}

var test1 = test()
console.log(test1)

这样循环每次执行时,将i作为参数实时传到内部函数中,这样便实现了保存每次i的值。但是和上面函数不一样的地方是,我们这里获得的test1,实际是一个由数字组成的数组,而不是函数,这我们的目标是不符的。
我们需要的是数组内每个函数都能保存其创建时的i值,那么简单,在之前立即执行函数的基础上,让它的返回值也成为一个函数就行,而参数既然能往下传一层,当然也可以传两层,于是我们动手:


var test = function () {
  var arr = []
  for(var i = 0; i < 5; i++){
    arr.push(function (n) {
      return function () {
        return n * n
      }
    }(i))
  }
  return arr
}

var test1 = test()
console.log(test1[0]())
console.log(test1[1]())
console.log(test1[2]())

结果为:0, 1, 4。
可以看到,我们只是在之前的基础上进行了一点加工,让外层匿名函数的返回时也变为一个函数,让内层函数的返回值为我们需要的结果,由于内层函数保持着对传进来的参数i的引用,导致i的值一直保存在内存中没有被释放,使得我们在调用这个函数的时候仍然能获得每次i的值,这也就是我们所说的闭包的实现。

阶段小结

由上面我们能够获得这样的结论:闭包能够保存变量的引用,使得函数内部的变量在其他地方也能够使用,但是弊端也存在,会显著消耗内存。

看看ES6

我们在ES6中获得了两个新的声明方式:letconst,这里我们仅讨论let。
先来看看MDN的说明:

let允许你声明一个作用域被限制在块级中的变量、语句或者表达式。与var关键字不同的是,var声明的变量只能是全局或者整个函数块的。

我们看到了什么?看到了之前在JavaScript中不存在的块级作用域,替代该死的、有极大误导性的函数作用域。现在我们拥有了let,该怎样实现之前的例子呢?简单到你无法想象:

var test = function () {
  const arr = []
  for(let i = 0; i < 5; i++){
    arr.push(function () {
      return i*i
    })
  }
  return arr
}

var test1 = test()
console.log(test1[0]())
console.log(test1[1]())
console.log(test1[2]())

结果:0, 1, 4.
就是如此简单,和最开始相比,整个函数我们只替换了2处:const arr = []以及let i = 0,由于let声明的块级作用域,每次循环的i都会被直接固定下来而不会受其他地方的影响,轻松实现了闭包的效果。
所以在ES6环境下,我们不需要用立即执行函数的形式来hack实现闭包,只需要使用新的声明方式,一切迎刃而解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容