js知识点之闭包

闭包

什么是闭包

闭包,是 JavaScript 中一个非常重要的知识点,也是我们前端面试中较高几率被问到的知识点之一。

打开《JavaScript 高级程序设计》和《 JavaScript 权威指南》,会发现里面针对闭包的解释各执一词,在网络上搜索关于闭包的内容,也发现众说纷纭,这就导致了这个知识点本身显得有点神秘,甚至还有一点玄幻。

那么这个知识点真的有那么深奥么?

非也!其实要理解 JavaScript 中的闭包,非常容易,但是在此之前你需要先知道以下两个知识点:

  • JavaScript 中的作用域和作用域链
  • JavaScript 中的垃圾回收

这里我们来简单回顾一下这两个知识点:

1. JavaScript 中的作用域和作用域链

  • 作用域就是一个独立的地盘,让变量不会外泄、暴露出去,不同作用域下同名变量不会有冲突。
  • 作用域在定义时就确定,并且不会改变。
  • 如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

2. JavaScript 中的垃圾回收

  • Javascript 执行环境会负责管理代码执行过程中使用的内存,其中就涉及到一个垃圾回收机制
  • 垃圾收集器会定期(周期性)找出那些不再继续使用的变量,只要该变量不再使用了,就会被垃圾收集器回收,然后释放其内存。如果该变量还在使用,那么就不会被回收。

OK,有了这 2 个知识点的铺垫后,接下来我们再来看什么是闭包。

闭包不是一个具体的技术,而是一种现象,是指在定义函数时,周围环境中的信息可以在函数中使用。换句话说,执行函数时,只要在函数中使用了外部的数据,就创建了闭包。

而作用域链,正是实现闭包的手段。

什么?只要在函数中使用了外部的数据,就创建了闭包?

真的是这样么?下面我们可以证明一下:

image-20211227145016552

在上面的代码中,我们在函数 a 中定义了一个变量 i,然后打印这个 i 变量。对于 a 这个函数来讲,自己的函数作用域中存在 i 这个变量,所以我们在调试时可以看到 Local 中存在变量 i

下面我们将上面的代码稍作修改,如下图:

image-20211227145521272

在上面的代码中,我们将声明 i 这个变量的动作放到了 a 函数外面,也就是说 a 函数在自己的作用域已经找不到这个 i 变量了,它会怎么办?

学习了作用域链的你肯定知道,它会顺着作用域链一层一层往外找。然而上面在介绍闭包时说过,如果出现了这种情况,也就是函数使用了外部的数据的情况,就会创建闭包。

仔细观察调试区域,我们会发现此时的 i 就放在 Closure 里面的,从而证实了我们前面的说法。

那么是一个函数下所有的变量声明都会被放入到闭包这个封闭的空间里面么?

倒也不是,放不放入到闭包中,要看其他地方有没有对这个变量进行引用,例如:

image-20211227164333723

在上面的代码中,函数 c 中一个变量都没有创建,却要打印 i、j、kx,这些变量分别存在于 a、b 函数以及全局作用域中,因此创建了 3 个闭包,全局闭包里面存储了 i 的值,闭包 a 中存储了变量 jk 的值,闭包 b 中存储了变量 x 的值。

但是你仔细观察,你就会发现函数 b 中的 y 变量并没有被放在闭包中,所以要不要放入闭包取决于该变量有没有被引用。

当然,此时的你可能会有这样的一个新问题,那么多闭包,那岂不是占用内存空间么?

实际上,如果是自动形成的闭包,是会被销毁掉的。例如:

image-20211227174043786

在上面的代码中,我们在第 16 行尝试打印输出变量 k,显然这个时候是会报错的,在第 16 行打一个断点调试就可以清楚的看到,此时已经没有任何闭包存在,垃圾回收器会自动回收没有引用的变量,不会有任何内存占用的情况。

当然,这里我指的是自动产生闭包的情况,关于闭包,有时我们需要根据需求手动的来制造一个闭包。

来看下面的例子:

function eat(){
    var food = "鸡翅";
    console.log(food);
}
eat(); // 鸡翅
console.log(food); // 报错

在上面的例子中,我们声明了一个名为 eat 的函数,并对它进行调用。

JavaScript 引擎会创建一个 eat 函数的执行上下文,其中声明 food 变量并赋值。

当该方法执行完后,上下文被销毁,food 变量也会跟着消失。这是因为 food 变量属于 eat 函数的局部变量,它作用于 eat 函数中,会随着 eat 的执行上下文创建而创建,销毁而销毁。所以当我们再次打印 food 变量时,就会报错,告诉我们该变量不存在。

但是我们将此代码稍作修改:

function eat(){
    var food = '鸡翅';
    return function(){
        console.log(food);
    }
}
var look = eat();
look(); // 鸡翅
look(); // 鸡翅

在这个例子中,eat 函数返回一个函数,并在这个内部函数中访问 food 这个局部变量。调用 eat 函数并将结果赋给 look 变量,这个 look 指向了 eat 函数中的内部函数,然后调用它,最终输出 food 的值。

为什么能访问到 food,原因很简单,上面我们说过,垃圾回收器只会回收没有被引用到的变量,但是一旦一个变量还被引用着的,垃圾回收器就不会回收此变量。在上面的示例中,照理说 eat 调用完毕 food 就应该被销毁掉,但是我们向外部返回了 eat 内部的匿名函数,而这个匿名函数有引用了 food,所以垃圾回收器是不会对其进行回收的,这也是为什么在外面调用这个匿名函数时,仍然能够打印出 food 变量的值。

至此,闭包的一个优点或者特点也就体现出来了,那就是:

  • 通过闭包可以让外部环境访问到函数内部的局部变量。
  • 通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。

通过此特性,我们可以解决一个全局变量污染的问题。早期在 JavaScript 还无法进行模块化的时候,在多人协作时,如果定义过多的全局变量 有可能造成全局变量命名冲突,使用闭包来解决功能对变量的调用将变量写到一个独立的空间里面,从而能够一定程度上解决全局变量污染的问题。

例如:

var name = "GlobalName";
// 全局变量
var init = (function () {
    var name = "initName";
    function callName() {
        console.log(name);
        // 打印 name
    }
    return function () {
        callName();
        // 形成接口
    }
}());
init(); // initName
var initSuper = (function () {
    var name = "initSuperName";
    function callName() {
        console.log(name);
        // 打印 name
    }
    return function () {
        callName();
        // 形成接口
    }
}());
initSuper(); // initSuperName

好了,在此小节的最后,我们来对闭包做一个小小的总结:

  • 闭包是一个封闭的空间,里面存储了在其他地方会引用到的该作用域的值,在 JavaScript 中是通过作用域链来实现的闭包。

  • 只要在函数中使用了外部的数据,就创建了闭包,这种情况下所创建的闭包,我们在编码时是不需要去关心的。

  • 我们还可以通过一些手段手动创建闭包,从而让外部环境访问到函数内部的局部变量,让局部变量持续保存下来,不随着它的上下文环境一起销毁。

闭包经典问题

聊完了闭包,接下来我们来看一个闭包的经典问题。

for (var i = 1; i <= 3; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

在上面的代码中,我们预期的结果是过 1 秒后分别输出 i 变量的值为 1,2,3。但是,执行的结果是:4,4,4

实际上,问题就出在闭包身上。你看,循环中的 setTimeout 访问了它的外部变量 i,形成闭包。

i 变量只有 1 个,所以循环 3 次的 setTimeout 中都访问的是同一个变量。循环到第 4 次,i 变量增加到 4,不满足循环条件,循环结束,代码执行完后上下文结束。但是,那 3setTimeout1 秒钟后才执行,由于闭包的原因,所以它们仍然能访问到变量 i,不过此时 i 变量值已经是 4 了。

要解决这个问题,我们可以让 setTimeout 中的匿名函数不再访问外部变量,而是访问自己内部的变量,如下:

for (var i = 1; i <= 3; i++) {
    (function (index) {
        setTimeout(function () {
            console.log(index);
        }, 1000);
    })(i)
}

这样 setTimeout 中就可以不用访问 for 循环声明的变量 i 了。而是采用调用函数传参的方式把变量 i 的值传给了 setTimeout,这样它们就不再创建闭包,因为在我自己的作用域里面能够找到 i 这个变量。

当然,解决这个问题还有个更简单的方法,就是使用 ES6 中的 let 关键字。

它声明的变量有块作用域,如果将它放在循环中,那么每次循环都会有一个新的变量 i,这样即使有闭包也没问题,因为每个闭包保存的都是不同的 i 变量,那么刚才的问题也就迎刃而解。

for (let i = 1; i <= 3; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

真题解答

  • 闭包是什么?闭包的应用场景有哪些?怎么销毁闭包?

闭包是一个封闭的空间,里面存储了在其他地方会引用到的该作用域的值,在 JavaScript 中是通过作用域链来实现的闭包。

只要在函数中使用了外部的数据,就创建了闭包,这种情况下所创建的闭包,我们在编码时是不需要去关心的。

我们还可以通过一些手段手动创建闭包,从而让外部环境访问到函数内部的局部变量,让局部变量持续保存下来,不随着它的上下文环境一起销毁。

使用闭包可以解决一个全局变量污染的问题。

如果是自动产生的闭包,我们无需操心闭包的销毁,而如果是手动创建的闭包,可以把被引用的变量设置为 null,即手动清除变量,这样下次 JavaScript 垃圾回收器在进行垃圾回收时,发现此变量已经没有任何引用了,就会把设为 null 的量给回收了。


-EOF-

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/662031.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【PMP必考点】2024年PMP敏捷知识必考点-工件篇

PMP自2023年8月起启动了PMBOK第七版教材&#xff0c;考试内容的侧重点也发生了改变&#xff1a;大幅增加了敏捷相关的内容。以往考纲只有不到10%的敏捷管理的内容&#xff0c;新考纲的敏捷管理题目增加至50%。所以如果复习只用之前的题目是远远不够的&#xff0c;必须要多刷新考…

2024年西安交通大学程序设计校赛

A题 签到题 代码如下 //A #include<iostream> #include<algorithm> #define int long long #define endl \n #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); using namespace std; signed main() {IOSint a,b,c,d;cin>>a>>b>>c…

使用C语言openssl库实现 RSA加密 和 消息验证

Q&#xff1a;什么是RSA&#xff1f; A&#xff1a;RSA&#xff08;Rivest-Shamir-Adleman&#xff09;是一种非对称加密算法&#xff0c;是最早的一种用于公开密钥加密和数字签名的算法。它使用一对公钥&#xff08;public key&#xff09;和私钥&#xff08;private key&…

Gartner发布2024年数据与分析重要趋势

Gartner公司发布了2024年数据与分析&#xff08;D&A&#xff09;重要趋势&#xff0c;这些趋势正在带来包括组织、人事问题在内的各种挑战。 Gartner高级研究总监方琦表示&#xff1a;“AI的力量以及日益重要的生成式AI正在改变人们的工作方式、团队协作方式和流程运作方式…

每天学点小知识:Windows终端Powershell美化

前言 本章的旨在教会你美化自己的终端&#xff0c;powershell需要以管理员运行 经过我的测试&#xff0c;不同的电脑可能会有不同的报错&#xff0c;具体操作根据官方为主https://ohmyposh.dev/docs 效果展示 Oh My Posh&#xff1a;提供美观的 PowerShell 提示符主题 1.安装…

LWIP_TCP 协议

目录 1 TCP 协议简介 1.1 TCP 协议简介 1.2 TCP 的建立连接 1.3 TCP 终止连接 1.4 TCP 报文结构 1.5 lwIP 的 TCP 报文首部数据结构 1.6 lwIP 的 TCP 连接状态图 1 TCP 协议简介 1.1 TCP 协议简介 TCP&#xff08;Transmission Control Protocol 传输控制协议&#xff0…

鸿蒙时间滑动选择器弹窗

例子&#xff1a; Button(打开弹窗).fontSize(14).width(106).height(32).padding({ left: 0, right: 0 }).fontColor(#999).onClick(()>{DatePickerDialog.show({selected:new Date(),onDateAccept:(value)>{AlertDialog.show({ message:JSON.stringify(value) })}})}) …

【豆伴匠】L1-L12更新完,一站式解决文史积累、阅读、写作难题,弯道超车,寒假必备

合抱之木&#xff0c;生于毫末&#xff1b; 九层之台&#xff0c;起于垒土&#xff1b; 千里之行&#xff0c;始于足下。 豆伴匠是什么&#xff1f; 豆伴匠内容包括&#xff1a;人、文、史、作四个模块&#xff0c;全面覆盖文史知识及读写技巧。 目前&#xff0c;豆伴匠有L…

[猫头虎分享21天微信小程序基础入门教程]第21天:小程序的社交分享与消息推送

[猫头虎分享21天微信小程序基础入门教程]第21天&#xff1a;小程序的社交分享与消息推送 第21天&#xff1a;小程序的社交分享与消息推送 &#x1f4f2; 自我介绍 大家好&#xff0c;我是猫头虎&#xff0c;一名全栈软件工程师。今天我们继续微信小程序的学习&#xff0c;重…

论文《Visual Point Cloud Forecasting enables Scalable Autonomous Driving》详细解析

论文《Visual Point Cloud Forecasting enables Scalable Autonomous Driving》详细解析 摘要 该论文提出了一种新的预训练任务&#xff0c;称为“视觉点云预测”&#xff08;Visual Point Cloud Forecasting&#xff09;&#xff0c;从历史视觉输入中预测未来的点云。论文介…

大模型部署推理应用技术浅析

大模型完成预训练后不是就万事大吉了&#xff0c;离推理应用还有很大距离&#xff0c;需要经过微调、部署等一系列工程化工作。尤其是在2B的行业大模型应用中&#xff0c;为解决大模型的幻觉、时效性和推理成本问题&#xff0c;需要建立单一模型之上的体系。模型部署中的技术大…

CyberLink ColorDirector Ultra v12 解锁版安装教程 (视频后期调色软件)

前言 CyberLink ColorDirector Ultra 是一款视频后期调色处理软件&#xff0c;可以给视频进行专业色彩调整&#xff0c;让视频看起来焕然一新。它支持的格式视频非常多&#xff0c;流行的格式全都可以添加到里面进行调色处理&#xff0c;主要是使用关键帧控件进行颜色替换&…

Mac在docker可视化界面上安装主流数据库

前言 篇幅有点长&#xff0c;大家可以打开目录快速跳转到想要的数据库即可&#xff01; 虽然说用命令行会显得我们更加专业一些&#xff0c;但对于我英语水平不怎么好的人来说&#xff0c;毕竟命令多又长&#xff0c;还不好记。我个人是喜欢复杂问题简单化&#xff0c;踩了很多…

Balabolka-一款完全免费的电子书朗读及文本转语音软件

下载地址&#xff1a;Balabolka Balabolka能够进行简单的文本转语音任务&#xff0c;支持各种语言包&#xff0c;该软件可以读取剪贴板的内容&#xff0c; 可以查看AZW&#xff0c;CHM&#xff0c;DjVu&#xff0c;DOC&#xff0c;EPUB&#xff0c;FB2&#xff0c;LIT&#xff…

想知道股指期货和期权有什么不同吗?

市场上目前有中金所的沪深300ETF&#xff0c;中证500和中证1000股指期货&#xff0c;期权市场有上证50ETF&#xff0c;沪深300etf和中证500ETF期权&#xff0c;股指期货和期权在买卖双方的权利义务、风险收益特征、保证金制度、上市合约数量等方面均有较大区别&#xff0c;下文…

操作系统真象还原:一些你可能正感到迷惑的问题

第0章-一些你可能正感到迷惑的问题 这是我看操作系统真象还原这本书的一些记录&#xff1a; 4 软件是如何访问硬件的 硬件在输入输出上大体分为串行和并行&#xff0c;相应的接口也就是串行接口和并行接口。串行硬件通过串行接口与 CPU 通信&#xff0c;反过来也是&#xff…

连锁收银系统支持带结算功能

连锁实体店的收银系统需要支持结算功能&#xff0c;以适应连锁运营效率和提升连锁管理的水平。商淘云连锁收银系统与您一起分享连锁收银系统需支持结算功能的三大必要点。大家点赞收藏&#xff0c;以免划走后找不到了。 一是&#xff0c;连锁模式的运营比较复杂&#xff0c;有加…

2021CSP-J普及组复赛-第一题:分糖果

2021CSP-J普及组复赛 第一题&#xff1a; 题目&#xff1a; 输入&#xff1a; 7 16 23输出&#xff1a; 6思路&#xff1a; 这是一个简单的思考题&#xff0c;没有用到重要的算法 ①简单的思路即暴力方法就是利用for循环从L 到 R 遍历求出其中最大的奖励值&#xff0c;由于R…

2024最新 Jenkins + Docker实战教程(七)- Jenkins实现远程传输和自动部署

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

论文阅读:Correcting Motion Distortion for LIDAR HD-Map Localization

目录 概要 Motivation 整体架构流程 技术细节 小结 论文地址&#xff1a;http://arxiv.org/pdf/2308.13694.pdf 代码地址&#xff1a;https://github.com/mcdermatt/VICET 概要 激光雷达的畸变矫正是一个非常重要的工作。由于扫描式激光雷达传感器需要有限的时间来创建…