声明

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

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

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),公众号中有我的联系方式,欢迎有事没事来唠嗑一下,如果你觉得本篇内容有帮助到你,可以转载但记得要关注,要标明原文哦,谢谢支持~

前端入门19-JavaScript进阶之闭包的更多相关文章

  1. 前端面试之JavaScript中的闭包!

    前端面试之JavaScript中的闭包! 闭包 闭包( closure )指有权访问另一个函数作用域中变量的函数. ----- JavaScript 高级程序设计 闭包其实可以理解为是一个函数 简单理 ...

  2. 前端基础之JavaScript进阶

    一.流程控制 if - else var a = 10; if (a >5){ console.log("yes"); }else { console.log("n ...

  3. 【进阶2-3期】JavaScript深入之闭包面试题解

    这是我在公众号(高级前端进阶)看到的文章,现在做笔记 https://github.com/yygmind/blog/issues/19 作用域指的是一个变量和函数的作用范围,JS中函数内声明的所有变 ...

  4. JavaScript进阶系列01,函数的声明,函数参数,函数闭包

    本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...

  5. javascript进阶课程--第三章--匿名函数和闭包

    javascript进阶课程--第三章--匿名函数和闭包 一.总结 二.学习要点 掌握匿名函数和闭包的应用 三.匿名函数和闭包 匿名函数 没有函数名字的函数 单独的匿名函数是无法运行和调用的 可以把匿 ...

  6. javascript进阶教程第三章--匿名和闭包--案例实战

    javascript进阶教程第三章--匿名和闭包--案例实战 一.学习任务 通过几个小练习回顾学过的知识点 二.实例 练习1: 实例描述:打开页面后规定时间内弹出一个新窗口,新窗口指定时间后自动关闭. ...

  7. 结合个人经历总结的前端入门方法 (转自https://github.com/qiu-deqing/FE-learning)

    结合个人经历总结的前端入门方法 (https://github.com/qiu-deqing/FE-learning),里面有很详细的介绍. 之前一直想学习前端的,都不知道怎么下手都一年了啥也没学到, ...

  8. 2019年Web前端入门的自学路线

    本文最初发表于博客园,并在GitHub上持续更新前端的系列文章.欢迎在GitHub上关注我,一起入门和进阶前端. 以下是正文.本文内容不定期更新. 我前几天写过一篇文章:<裸辞两个月,海投一个月 ...

  9. 深入理解javascript原型和闭包系列

    从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然不至于上头条,但是也算是中规中矩,有看的人,也有评 ...

  10. 4、JavaScript进阶篇①——基础语法

    一.认识JS 你知道吗,Web前端开发师需要掌握什么技术?也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HTML+CSS创建一个漂亮的页面,但这还不够,它只是静态页面 ...

随机推荐

  1. UML建模工具

    UML:Unified Modeling Language (UML)又称统一建模语言或标准建模语言,是始于1997年一个OMG标准,它是一个支持模型化和软件系统开发的图形化语言,为软件开发的所有阶段 ...

  2. Golang Go Go Go part2:变量及常量声明

    三.关键字及内置预声明常量.类型.函数 1.关键字 Go有25个关键字,只能用在语法允许的地方,不能作为名称使用,它们是: break          default         func    ...

  3. C#等同于正则表达式的写法

    不用写正则表达式,用C#自带的方法 用char 自带的函数实现 /// <summary> /// 只能由数字和大小写字母组成 /// </summary> /// <p ...

  4. Spring框架中<constructor-arg>与<property>理解

    配置文件的根元素是beans,每个组件使用bean元素来定义,bean元素可以有许多属性,其中有两个是必须的:id和class.id表示组件的默认名称,class表示组件的类型. 依赖注入的方式:  ...

  5. ASP.NET Core Web App应用第三方Bootstrap模板

    引言 作为后端开发来说,前端表示玩不转,我们一般会选择套用一些开源的Bootstrap 模板主题来进行前端设计.那如何套用呢?今天就简单创建一个ASP.NET Core Web MVC 模板项目为例, ...

  6. 安卓开发学习笔记(一):如何用Android Stuidio导出apk文件?

    一,首先,我们在菜单栏上找到这一栏: 然后点击build,再点击generate apk,然后出现以下界面: 由于之前我们并没有进行apk文件的生成,因此需要这个apk key做一个验证,以防您的ap ...

  7. Javascript中的this关键字用法详解

    在javascript里面,this是一个特殊的对象,它不像其他编程语言那样,是存储在实例中的值,直接指向此实例. 而是作为一个单独的指针,在不同的情况之下,指向不同的位置,这也是为什么我们会将它搞混 ...

  8. 理解Golang哈希表Map的元素

    目录 概述 哈希函数 冲突解决 初始化 结构体 字面量 运行时 操作 访问 写入 扩容 删除 总结 在上一节中我们介绍了 数组和切片的实现原理,这一节会介绍 Golang 中的另一个集合元素 - 哈希 ...

  9. [Java]LeetCode278. 第一个错误的版本 | First Bad Version

    You are a product manager and currently leading a team to develop a new product. Unfortunately, the ...

  10. [Swift]LeetCode801. 使序列递增的最小交换次数 | Minimum Swaps To Make Sequences Increasing

    We have two integer sequences A and B of the same non-zero length. We are allowed to swap elements A ...