闭包以及其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实现闭包,只需要使用新的声明方式,一切迎刃而解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容