前端入门19-JavaScript进阶之闭包
声明
本系列文章内容全部梳理自以下几个来源:
- 《JavaScript权威指南》
- MDN web docs
- Github:smyhvae/web
- Github:goddyZhao/Translation/JavaScript
作为一个前端小白,入门跟着这几个来源学习,感谢作者的分享,在其基础上,通过自己的理解,梳理出的知识点,或许有遗漏,或许有些理解是错误的,如有发现,欢迎指点下。
PS:梳理的内容以《JavaScript权威指南》这本书中的内容为主,因此接下去跟 JavaScript 语法相关的系列文章基本只介绍 ES5 标准规范的内容、ES6 等这系列梳理完再单独来讲讲。
正文-闭包
在作用域链那篇中,稍微留了个闭包的念想,那么这篇就来讲讲什么是闭包。
概念
这个闭包的概念蛮不好理解的,我在阮一峰的某篇文章中看过大概这么句话,闭包是对英文单词的直译,在中文里没有与之对应的句子解释,因此很难理解闭包究竟指的是什么。
看过很多解释,有说闭包就是函数;也有说闭包就是代码块;还有说函数内的函数就称闭包;还有说当函数返回内部某个函数时,返回的这个函数叫闭包,也有说闭包就是能够读取其他函数内部数据(变量/函数)的函数。
MDN 网站里不同文章里出现过多种解释:
- 一个闭包是一个可以自己拥有独立的环境与变量的表达式(通常是函数)
- 闭包是函数和声明该函数的词法环境的组合,这个环境包含了这个闭包创建时所能访问的所有局部变量
另外,在某篇文章中,看过这么段话:
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进阶之闭包的更多相关文章
- 前端面试之JavaScript中的闭包!
前端面试之JavaScript中的闭包! 闭包 闭包( closure )指有权访问另一个函数作用域中变量的函数. ----- JavaScript 高级程序设计 闭包其实可以理解为是一个函数 简单理 ...
- 前端基础之JavaScript进阶
一.流程控制 if - else var a = 10; if (a >5){ console.log("yes"); }else { console.log("n ...
- 【进阶2-3期】JavaScript深入之闭包面试题解
这是我在公众号(高级前端进阶)看到的文章,现在做笔记 https://github.com/yygmind/blog/issues/19 作用域指的是一个变量和函数的作用范围,JS中函数内声明的所有变 ...
- JavaScript进阶系列01,函数的声明,函数参数,函数闭包
本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...
- javascript进阶课程--第三章--匿名函数和闭包
javascript进阶课程--第三章--匿名函数和闭包 一.总结 二.学习要点 掌握匿名函数和闭包的应用 三.匿名函数和闭包 匿名函数 没有函数名字的函数 单独的匿名函数是无法运行和调用的 可以把匿 ...
- javascript进阶教程第三章--匿名和闭包--案例实战
javascript进阶教程第三章--匿名和闭包--案例实战 一.学习任务 通过几个小练习回顾学过的知识点 二.实例 练习1: 实例描述:打开页面后规定时间内弹出一个新窗口,新窗口指定时间后自动关闭. ...
- 结合个人经历总结的前端入门方法 (转自https://github.com/qiu-deqing/FE-learning)
结合个人经历总结的前端入门方法 (https://github.com/qiu-deqing/FE-learning),里面有很详细的介绍. 之前一直想学习前端的,都不知道怎么下手都一年了啥也没学到, ...
- 2019年Web前端入门的自学路线
本文最初发表于博客园,并在GitHub上持续更新前端的系列文章.欢迎在GitHub上关注我,一起入门和进阶前端. 以下是正文.本文内容不定期更新. 我前几天写过一篇文章:<裸辞两个月,海投一个月 ...
- 深入理解javascript原型和闭包系列
从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然不至于上头条,但是也算是中规中矩,有看的人,也有评 ...
- 4、JavaScript进阶篇①——基础语法
一.认识JS 你知道吗,Web前端开发师需要掌握什么技术?也许你已经了解HTML标记(也称为结构),知道了CSS样式(也称为表示),会使用HTML+CSS创建一个漂亮的页面,但这还不够,它只是静态页面 ...
随机推荐
- Signed Distance Field Shadow in Unity
0x00 前言 最近读到了一个今年GDC上很棒的分享,是Sebastian Aaltonen带来的利用Ray-tracing实现一些有趣的效果的分享. 其中有一段他介绍到了对Signed Distan ...
- Java打包商用化软件
这是我在博客中写的第一篇文章.还请各位大神们多多指教!我会详细讲解如何将我们由java的swing以及awt组件编写出的java可视化窗口程序编制成一个我们能够让用户使用的,商业化,可安装的软件.网上 ...
- 操作系统中 heap 和 stack 的区别
操作系统中 heap 和 stack 的区别heap 和 stack是什么堆栈是两种数据结构.堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除.==在单片机 ...
- Web前端-Vue.js必备框架(三)
Web前端-Vue.js必备框架(三) vue是一款渐进式javascript框架,由evan you开发.vue成为前端开发的必备之一. vue的好处轻量级,渐进式框架,响应式更新机制. 开发环境, ...
- 微信小程序快速开发上手
微信小程序快速开发上手 介绍: 从实战开发角度,完整系统地介绍了小程序的开发环境.小程序的结构.小程序的组件与小程序的API,并提供了多个开发实例帮助读者快速掌握小程序的开发技能,并能自己动手开发出小 ...
- [Swift]LeetCode269. 外星人词典 $ Alien Dictionary
There is a new alien language which uses the latin alphabet. However, the order among letters are un ...
- [Swift]LeetCode770. 基本计算器 IV | Basic Calculator IV
Given an expression such as expression = "e + 8 - a + 5" and an evaluation map such as {&q ...
- 优化之Joiner组件
Joiner组件在运行时需要额外的内存空间处理中间结果,因此会影响性能 可通过查看Joiner performance计数器来决定Joiner组件是否需要优化 通过如下方式优化Joiner组件 将Ma ...
- [bzoj4771] 七彩树
题意 给定一棵n个点,每个点带颜色的有根树.点的编号和颜色编号都在1到n,根的编号为1.m次询问,求x子树中与x距离边数不超过k的点中,颜色的种类数目.每个测试点有多组数据. 分析 不妨设1的父亲为0 ...
- 解决Eclipse中无法查看Java源码
1.点 "window"-> "Preferences"-> "Java" -> "Installed JRES ...