闭包、闭包应用场景

article/2025/11/6 8:34:54

什么是闭包

要理解 JavaScript 中的闭包,需要先知道以下两个知识点:

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

回顾这两个知识点:

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

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

2. JavaScript 中的垃圾回收

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

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

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

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

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

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

在这里插入图片描述

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

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

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

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

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

当一个词特别难理解的时候,可以使用拆词法:“闭”可以理解为“封闭,闭环”,“包”可以理解为“一个类似于包裹的空间”,因此闭包实际上可以看作是一个封闭的空间,那么这个空间用来干啥呢?实际上就是用来存储变量的。

image-20211227163947135

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

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

在这里插入图片描述

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

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

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

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

在这里插入图片描述

在上面的代码中,我们在第 16 行尝试打印输出变量 x,显然这个时候是会报错的,在第 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 的量给回收了。


以上笔记整理于渡一教育谢老师课堂


http://chatgpt.dhexx.cn/article/lXvCNwG5.shtml

相关文章

闭包是什么?五分钟带你了解闭包

闭包 前言 闭包对每个前端来说都是一个绕不开的话题。学习之初也因为搞清闭包的概念耗费了不少精力&#xff0c;今天写一篇博客来记录本人对闭包的理解&#xff0c;笔者水平有限&#xff0c;若有疏漏及错误&#xff0c;愿不吝赐教。 什么是闭包&#xff1f; 你可以在一个函…

闭包:什么是闭包、闭包的作用、闭包的解决

1、什么是闭包 学习闭包我们要清楚函数作用域、内存回收机制、作用域继承。 1.1 函数作用域 作用域我们可以认为它是一个封闭的盒子&#xff0c;只让它在这个盒子里面进行操作&#xff0c;也可以称这个盒子为独立作用域。在js中&#xff0c;一个函数要执行时就会在内存里面创…

基于SSH的学生考勤管理系统

020基于SSH的学生考勤管理系统 开发环境&#xff1a; Jdk7(8)Tomcat7(8)MysqlIntelliJ IDEA(Eclipse) 数据库&#xff1a; MySQL 技术&#xff1a; SpringStruts2HiberanteJqueryJavaScriptAjaxJSPBootstrap 适用于&#xff1a; 课程设计&#xff0c;毕业设计&#xff0c;学…

springboot学生考勤管理系统

032-springboot学生考勤管理系统演示录像2022 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&…

java管理系统课设,基于Java的学生考勤管理系统设计毕业设计

基于Java的学生考勤管理系统设计毕业设计 本科生毕业论文(设计)本科生毕业论文(设计) 基于基于 JavaJava 的学生考勤管理的学生考勤管理系统设计系统设计 Design of Student Attendance Management System Based on Java 专 业 电子信息工程 姓 名 学 号 指 导 教 师 完 成 时…

学生考勤及行为管理系统_学生考勤管理系统_考勤管理系统-先知科技

考勤管理系统简介&#xff1a; 先知智慧校园考勤管理系统帮助各大院校解决教职工考勤&#xff0c;学生上课考勤多种方式考勤。可通过手机端app考勤&#xff0c;终端考勤机器考勤&#xff0c;刷卡通过人形通道考勤&#xff0c;电子班牌考勤&#xff0c;人脸识别考勤多种方式正对…

基于php013学生考勤管理系统

经过查阅关于高校考勤系统的优秀毕业设计&#xff0c;使我对基于PHP的学生考勤管理系统的设计有了进一步了解&#xff0c;大致分为三个大模块&#xff1a;学生模块&#xff0c;教师模块和管理员模块。系统为需要考勤的人员和学院提供不同权限的管理、查询、考勤等操作。考勤系统…

[附源码]java毕业设计基于的高校学生考勤管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

基于jsp学生考勤管理系统设计

考勤作为一个学校的基础管理&#xff0c;是对学生的个人出勤情况的依据。传统的考勤靠手工操作、纸质传递&#xff0c;这样的方式造成了考勤不全面、数据不准确和资料的共享程度低。因此学校需要一个可以适应大量信息控制和数据处理的考勤管理系统&#xff0c;用计算机的高效处…

MySQL做学生考勤系统_Jsp+Ssh+Mysql实现的Java Web学生考勤管理系统

JspSshMysql实现的Java Web学生考勤管理系统https://www.yuanlrc.com/product/details.html?pid164&fuid6666 实现了管理员、学生、教师三个角色的功能&#xff0c;其中管理员可以管理基本信息&#xff0c;如班级信息、课程信息、用户信息、课程表等。教师可以管理自己班级…

(附源码)ssm学生考勤管理系统 毕业设计 260952

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

C语言课程设计学生考勤管理系统

学生考勤管理系统 1、题目与要求 功能&#xff1a;设计一考勤系统。考勤信息记录了学生的缺课情况&#xff0c;它包括&#xff1a;缺课日期、第几节课、课程名称、学生姓名、学生学号、缺课类型&#xff08;迟到、早退、请假及旷课&#xff09;。系统具有以下功能&#xff1a…

C语言实现学生考勤管理系统

题目要求&#xff1a; 代码实现如下&#xff1a; 在这里插入代码片 #include<stdio.h> #include<stdlib.h> #include<string.h> int number;//全局变量&#xff0c;记录全部人的个数 //结构体&#xff0c;定义单个学生的信息 typedef struct student {i…

C++学生考勤管理系统报告

c课程设计学生考勤管理系统报告 1.需求分析 1.录入学生的缺课记录&#xff1b; 2&#xff0e;修改某个学生的缺课记录&#xff1b; 3&#xff0e;查询某个学生的缺课情况&#xff1b; 4&#xff0e;统计某段时间内&#xff0c;某门课旷课学生姓名及旷课次数&#xff0c;按旷课次…

学生考勤管理系统

学生考勤管理系统 学生考勤管理系统 背景分析&#xff1a; 随着高校校园信息化的逐步完善&#xff0c;有效地借助网络、数据库等技术提高工作和管理效率。如今针对师生的成绩查询系统、教务管理系统、招生就业系统、BBS、校园网站等系统在各大高校纷纷出现[1]&#xff0c;对全…

C语言课程设计——学生考勤管理系统

C语言课程设计——学生考勤管理系统 题目要求&#xff1a; 学生考勤管理系统设计 &#xff08;1&#xff09;问题描述 考勤信息记录了学生的缺课情况&#xff0c;它包括&#xff1a;缺课日期、第几节课、课程名称、学生姓名、缺课类型&#xff08;迟到、早退、请假及旷课&…

基于java学生考勤管理系统设计——计算机毕业设计

考勤作为一个学校的基础管理,是对学生的个人出勤情况的依据。传统的考勤靠手工操作、纸质传递,这样的方式造成了考勤不全面、数据不准确和资料的共享程度低。因此学校需要一个可以适应大量信息控制和数据处理的考勤管理系统,用计算机的高效处理方法和数据库的严谨结构代替手工操…

基于JavaSwing的学生考勤管理系统设计与实现

目录 前言 7 一、系统开发环境及相关技术 8 &#xff08;一&#xff09;系统设计思想及处理流程 8 &#xff08;二&#xff09;运行环境 8 &#xff08;三&#xff09;开发技术及开发工具简介 8 三、需求分析 10 &#xff08;一&#xff09;学生用户需求 10 &#xff08;二&…

《学生考勤信息管理系统》数据库课程设计

目录 一、 需求分析 前台功能模块 后台功能模块 1.1 功能模块的划分及介绍 1.2 实体及重要属性 1.3 业务流程图 二、 概念结构设计 2.1. E-R图的设计 三 、逻辑结构设计 表设计 User1-用户表 Student-学生信息表 College-院系信息表 Attendance personnel 考勤人员表 C…

多个div在同一行显示

使用float:left&#xff0c;也可以使用display : inline-block&#xff0c;可以使多个div在同一行显示。 <div class"search_row"><div class"form-group" style"float:left" > <%-- 通过左浮动使多个div在一行显示--%&g…