面试官问我:什么是JavaScript闭包,我该如何回答?
闭包,有人说它是一种设计理念,有人说所有的函数都是闭包。到底什么是闭包?这个问题在面试的时候经常都会被问,很多小白一听就懵逼了,不知道如何回答好。
这个问题也有很多朋友在公众号给李老师留言了,问题表达方式不一样,都是终归到一点,就是对闭包没有很清晰的理解。大家经常去网上找相关资料,但是对闭包的说法都是各种各样的,让大家对闭包的定义没有一个概念。
所以今天我们来一起讲讲什么是闭包,帮助大家理解,今天的内容可以直接收藏起来。方便以后看。
简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。
MDN 上面这么说:闭包是一种特殊的对象。
它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
这种官方的概念是比较难理解的,在面试的时候说出来也不是很专业,因为没办法有个具体的逻辑。
我个人认为,理解闭包的关键在于:外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象,这就是闭包的重要概念。
创建闭包最常见方式,就是在一个函数内部创建另一个函数。下面例子中的 closure 就是一个闭包:
闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。
通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。
从上述代码可以看到add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的环境。在 add5 的环境中,x 为 5。而在 add10 中,x 则为 10。最后通过 null 释放了 add5 和 add10 对闭包的引用。
在javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收;
如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。
大家看一下上面这个代码,arr数组中包含了10个匿名函数,每个匿名函数都能访问外部函数的变量i,那么i是多少呢?
当arrFunc执行完毕后,其作用域被销毁,但它的变量对象仍保存在内存中,得以被匿名访问,这时i的值为10。
要想保存在循环过程中每一个i的值,需要在匿名函数外部再套用一个匿名函数,在这个匿名函数中定义另一个变量并且立即执行来保存i的值。
这时最内部的匿名函数访问的是num的值,所以数组中10个匿名函数的返回值就是1-10。
闭包中的this对象
在上面这段代码中,obj.getName()()实际上是在全局作用域中调用了匿名函数,this指向了window。
这里要理解函数名与函数功能是分割开的,不要认为函数在哪里,其内部的this就指向哪里。
window才是匿名函数功能执行的环境。
如果想使this指向外部函数的执行环境,可以这样改写:
在闭包中,arguments与this也有相同的问题。下面的情况也要注意:
obj.getName();这时getName()是在对象obj的环境中执行的,所以this指向obj。
(obj.getName = obj.getName)赋值语句返回的是等号右边的值,在全局作用域中返回,所以(obj.getName = obj.getName)();的this指向全局。要把函数名和函数功能分割开来。
闭包会引用包含函数的整个变量对象,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁。所以我们有必要在对这个元素操作完之后主动销毁。
当函数内部的定时器引用了外部函数的变量对象时,该变量对象不会被销毁。
应用闭包的主要场合是:设计私有的方法和变量。
任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。私有变量包括函数的参数、局部变量和函数内定义的其他函数。
把有权访问私有变量的公有方法称为特权方法(privileged method)。
在这里,我们需要理解两个概念:
模块模式(The Module Pattern):为单例创建私有变量和方法。
单例(singleton):指的是只有一个实例的对象。JavaScript 一般以对象字面量的方式来创建一个单例对象。
上面是普通模式创建的单例,下面使用模块模式创建单例:
匿名函数最大的用途是创建闭包,并且还可以构建命名空间,以减少全局变量的使用。从而使用闭包模块化代码,减少全局变量的污染。
在这段代码中函数 addEvent 和 removeEvent 都是局部变量,但我们可以通过全局变量 objEvent 使用它,这就大大减少了全局变量的使用,增强了网页的安全性。
闭包引用外部函数变量对象中的值;
在外部函数的外部调用闭包。
闭包的缺点就是常驻内存会增大内存使用量,并且使用不当很容易造成内存泄露。
如果不是因为某些特殊任务而需要闭包,在没有必要的情况下,在其它函数中创建函数是不明智的,因为闭包对脚本性能具有负面影响,包括处理速度和内存消耗。
下面代码中,标记 ?的地方输出分别是什么?
大家结合今天讲解的内容,思考一下答案,大家可以把答案发到留意上吧。
好了,今天的讲解就那么多,如果你还有什么前端问题想提问的,或者你想李老师下次给大家讲什么内容,可以直接留意提问,说不定下次文章就会讲解了。
本文分享自微信公众号 - 前端研究所(WEBqdyjs)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
【微服务】144:跨域问题及其解决方法
今天是刘小爱自学Java的第144天。 感谢你的观看,谢谢你。 学习计划安排如下: 关于昨天编写的商品分类业务,访问方式的回顾以及补充,发现在管理系统中访问会报错。 浏览器直接访问没事,但是在管理系统中访问会报错,从而引出跨域问题。 使用CORS解决跨域问题,通过其解决流程回顾两个非常重要的知识点。 jdk8的新特性方法引用、以及SpringBoot中的Java配置类的回顾。 一、跨域问题的引出 对昨天商品分类业务回顾及补充: 昨天说明了第①种和第②种访问方式。 在后台服务器中编写代码接受对应的请求,从数据库中查询到对应数据并响应。 ①直接访问自然是没问题的。 ②通过网关,其相当于在直接访问的路径上做了一个保护,最终还是会跳转该路径。 其实第③种方式直接使用网关域名访问也是可以的,在前天使用nginx时配置了网关域名。 以上三种方式都没问题,证明代码本身编写的没有问题,但是在管理系统中发送请求时会发现浏览器会报错。 在前端页面中,点击分类管理,我们通过浏览器F12可以查看其发送的请求路径。 该路径和第③种访问方式是一样的。 为什么自己在浏览器中输入该路径能访问,但是在管理系统中发送请求...
- 下一篇
被 Google 选择的下一代数据面 Cilium 是什么 - 上手实践
背景 在我之前的文章 K8S 生态周报| Google 选择 Cilium 作为 GKE 下一代数据面[1] 一文中,我介绍了 Google 宣布使用 Cilium 作为 GKE 的下一代数据面,及其背后的故事。 Google 选择 Cilium 主要是为了增加 GKE 平台的容器安全性和可观测性。那么,Cilium 到底是什么,为什么会有这么强的吸引力呢? 摘一段官网的介绍: “ Cilium is open source software for transparently securing the network connectivity between application services deployed using Linux container management platforms like Docker and Kubernetes. ” Cilium 是一个用于透明保护部署在 Linux 容器管理平台(比如 Docker 和 Kubernetes)上的应用服务之间网络连接的开源软件。 为什么着重强调是 “Linux 容器管理平台” 呢?这就不得不提到 Cili...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- MySQL8.0.19开启GTID主从同步CentOS8
- Hadoop3单机部署,实现最简伪集群
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题