前端入门19-JavaScript进阶之闭包

声明

本系列文章内容全部梳理自以下几个来源:

作为一个前端小白,入门跟着这几个来源学习,感谢作者的分享,在其基础上,通过自己的理解,梳理出的知识点,或许有遗漏,或许有些理解是错误的,如有发现,欢迎指点下。

PS:梳理的内容以《JavaScript权威指南》这本书中的内容为主,因此接下去跟 JavaScript 语法相关的系列文章基本只介绍 ES5 标准规范的内容、ES6 等这系列梳理完再单独来讲讲。

正文-闭包

在作用域链那篇中,稍微留了个闭包的念想,那么这篇就来讲讲什么是闭包。

概念

这个闭包的概念蛮不好理解的,我在阮一峰的某篇文章中看过大概这么句话,闭包是对英文单词的直译,在中文里没有与之对应的句子解释,因此很难理解闭包究竟指的是什么。

看过很多解释,有说闭包就是函数;也有说闭包就是代码块;还有说函数内的函数就称闭包;还有说当函数返回内部某个函数时,返回的这个函数叫闭包,也有说闭包就是能够读取其他函数内部数据(变量/函数)的函数。

MDN 网站里不同文章里出现过多种解释:

  1. 一个闭包是一个可以自己拥有独立的环境与变量的表达式(通常是函数)
  2. 闭包是函数和声明该函数的词法环境的组合,这个环境包含了这个闭包创建时所能访问的所有局部变量

另外,在某篇文章中,看过这么段话:

2009年发布了ECMAScript-262-5th第五版,不同的是取消了变量对象和活动对象等概念,引入了词法环境(Lexical Environments)、环境记录(EnviromentRecord)等新的概念

所以如果对词法环境这个词不理解的,可以将其理解成执行上下文,或者作用域链。在开头声明给的第四个链接中,是有几篇很早很早之前大佬们翻译的国外的文章,里面对闭包的解释刚好和 MDN 的解释也很类似:

闭包是代码块和创建该代码块的上下文中数据的结合

如果这个代码块是函数,那么利用作用域链那篇中介绍的相关原理,从本质上看闭包:

函数代码,和函数的内部属性 [[Scope]] 两者的结合可称为闭包。 :

对于这么多文章中对闭包的这么多种解释,先不做评价,先来想想,为什么会有闭包,理清了后,你会发现,其实理解闭包没那么难。

闭包意义

先看个例子:

var num = 0;
function a() {
    var num = 1;
    function b() {
        console.log(num);
    }
    return b;
}
var c = a();
c();

调用 c() 输出的是 1,这点在作用域链那节已经讲解过了,这里再稍微说下:

调用 c(),会为 c 函数创建一个函数执行上下文,其中作用域链为:

c函数EC.VO –> a函数EC.VO -> 全局EC.VO

VO 是变量对象,表示存储着当前上下文中所有变量的对象,所以如果以 VO 的实际对象表示作用域链:

c函数{} –> a函数{num:1} -> 全局{num:0}

(忽略 VO 中其他与此例无关变量)

所以,函数 c 内的代码输出 num 时,到作用域链上寻找时,发现最后使用的是 a 函数内部的 num 变量,最终输出 1。

但当时也提了个疑问,当代码执行到 c() 时,a 函数已经执行结束,那么 a 函数的 EC 已经从执行环境栈 ECS 中被移出了,c 函数的 EC 里的作用域链为何还会有 a函数EC.VO 存在?

这就是闭包的典型场景了,闭包的意义之一就是解决这种场景。

通过作用域链一篇后,我们知道,函数内的变量依赖于函数执行上下文 EC,一般来说,当调用函数时,创建函数执行上下文 EC,并入栈 ECS,当函数执行结束时,就将 EC 从 ECS 中移出,并释放内存空间。

通常函数的行为的确是这样,但当函数如果有返回值时,情况就不一样了。虽然函数执行结束后它的 EC 确实被移出 ECS,但并没有被回收,JavaScript 解释器的垃圾回收机制也有引用计数的处理。

既然内存没被回收,那么 EC 就还存在,那么当调用 c() 时,虽然 C 的函数执行上下文是新创建的,上下文的作用域链也是新创建的,但作用域链的取值是当前执行上下文的 VO 拼接上函数对象的内部属性 [[Scope]]。

这个函数对象的内部属性 [[Scope]] 存储的就是这个函数的外层函数的执行上下文里的作用域链,它的值并不是新创建的,一直保存着外层函数调用时生成的外层函数上下文中的作用域链,通过它可以访问到外层函数变量。

再谈闭包概念

所以,实际上,网络上这么多文章里对闭包的各种解释,其实都没错。如果对作用域链的原理理解清楚后,你会发现,其实函数就是闭包,因为由于作用域的机制,让函数内部也持有创建函数的上下文的数据集合,所以函数符合闭包的特性。

只是在大部分场景下,函数执行结束,函数的 EC 就可以被回收,那么这种场景闭包并没有什么实际应用意义。

除了函数,如果你可以让某部分代码块持有创建它的上下文的数据集合,那么这也可以称为闭包。

常见的一种就是在函数内返回一个对象,对象的某些属性使用了对象外层的数据,如:

var model = (function () {
    var num = 1;
    return {
        num:num
    }
}());
model.num;

此时,也可以称返回的这个对象是闭包。

对于闭包,我对它的理解,更倾向于,闭包并不是一种机制,也不是一种具体的事物(如执行上下文),反而,闭包是对原本存在的事物满足某种场景下的一种称呼。

也就是说,闭包,它其实是在原有机制,原有事物上的另一种称呼。所以,网上也才有人会说,闭包是函数、闭包是内嵌的函数等等说法。其实,也不是说这是错的,他们有的是从闭包特性角度解释,有的是从闭包现象。

只是,这原本就存在的事物,你本可以就用它原本的称呼,既然想要用闭包来称呼它,那么自然是这个时候,称呼它为闭包有区别于原本事物的实际意义,所以也才有人会说当函数返回内部函数时,称为闭包,因为这种时候,返回的这个函数就是用到闭包的特性来解决某些问题,所以称这种现象为闭包当然就有实际应用场景意义了。

所以,我对闭包的理解,它并不是某个固定不变的东西,也不是某个具体的事物,只要符合闭包特性的原有事物,你都可以称它为闭包。所以,对于网上那些对闭包的解释,我的建议是,主谓互换一下,不要说闭包是函数,闭包是内嵌的函数等等,我们可以说,函数是闭包,内嵌的函数也是闭包。只要符合闭包特性的我们都可以称它为闭包,当然如果还有闭包的实际应用意义,那么称它为闭包更可以被人接受。

闭包的应用

作为外部和函数内部变量通信的桥梁

var model = (function () {
    var num = 1;
    function a() {
        console.log(num);
    }
    return {
        num:num
    }
}());
model.num;

外部是访问不了函数内部的信息,而闭包是指代码块持有创建它的上下文的数据集合。那么,如果在函数内部创建一个闭包,将这个闭包返回给外部,外部是否就可以通过这个闭包作为桥梁来间接与函数内部通信了。

封装

var Counter = (function() {
    var privateCounter = 0;
    function changeBy(val) {
        privateCounter += val;
    }
    return {
        increment: function() {
            changeBy(1);
        },
        decrement: function() {
            changeBy(-1);
        },
        value: function() {
            return privateCounter;
        }
    }
})();

还是同样的原因,外部是访问不了函数内部的信息,而闭包是指代码块持有创建它的上下文的数据集合。

那么,是否就可以借助闭包的特性,将一些实现封装在函数内部,通过闭包给外部提供有限的接口使用。

但要注意,函数本来执行结束,它的 EC 从 ECS 栈内移出时,通常就可被回收了,但如果用到了闭包的特性,导致外部持有着函数内部某个引用,此时函数的 EC 就不会被回收,那么就会占用着内存,使用不当,还会有可能造成内存泄漏。


大家好,我是 dasu,欢迎关注我的公众号(dasuAndroidTv),公众号中有我的联系方式,欢迎有事没事来唠嗑一下,如果你觉得本篇内容有帮助到你,可以转载但记得要关注,要标明原文哦,谢谢支持~


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