JS - 闭包(Closure)

目录

  • 1,什么是闭包
  • 2,创建闭包
  • 3,如何销毁闭包
    • 2.1,自动创建的闭包
    • 2.2,手动创建的闭包
  • 4,闭包的特点和使用场景
    • 3.1,特点
    • 3.2,使用场景
      • 避免全局变量污染
      • 函数柯里化
  • 5,闭包经典问题

闭包的定义有许多的说法,下面来介绍下我理解的。

1,什么是闭包

首先,在 js 中闭包是通过作用域链来实现的。

闭包可以看做是一个封闭的空间,用来存储当前作用域的变量,来在其他地方引用。

2,创建闭包

执行函数时,只要在函数中使用了外部数据,就会创建闭包

验证一下:

function fun() {const i = 1console.log(i) // 断点
}
fun()

在这里插入图片描述

const i = 1
function fun() {console.log(i)
}
fun()

在这里插入图片描述

而变量是否放入到闭包中,要看其他地方有没有对这个变量的引用。

举例:

const a = 1function fun() {const b = 2const c = 3function fun1() {const d = 4const e = 5function fun2() {console.log(a, b, c, d)}fun2()}fun1()
}
fun()

在这里插入图片描述

可以看到创建了3个闭包,分别存储对应作用域的变量。

  • 全局
  • fun
  • fun1

而变量 e 并没有被放到闭包中,因为没有被引用。

3,如何销毁闭包

闭包的产生会占用空间。那如何销毁闭包,释放空间?

创建闭包分为2种情况,销毁也有所区别。

  • 自动创建的闭包
  • 手动创建的闭包

2.1,自动创建的闭包

自动创建的闭包,在函数调用完会直接销毁掉

在上面的例子中,fun() 执行完成后,变量 a 在全局环境中(没有在全局闭包中)正常输出,变量b 会报错。

fun()
console.log(a) // 1
console.log(b) // Error

在这里插入图片描述

可以看到已经没有任何闭包存在了,垃圾回收器会自动回收没有引用的变量 b,c,d,e,不会有内存被占用的情况。

2.2,手动创建的闭包

手动创建的闭包,可以设置在函数调用完依然保留

先看一个例子:

function getUser() {const name = '下雪天的夏风'console.log(name);
}
getUser()
console.log(name); // Error

很明显,局部变量 name 会随着 getUser() 的执行上下文创建而创建,销毁而销毁。所以getUser()执行完后,name 也就不存在了,打印报错。

修改代码如下:

function getUser() {const name = '下雪天的夏风'return function () {console.log(name)}
}
const user = getUser()
user() // 下雪天的夏风

在这里插入图片描述

调用 user() 为什么能访问到 name,因为垃圾回收器只会回收没有被引用的变量。原本getUser()调用完,name就会被销毁掉,但此时向外部返回了一个匿名函数,该函数引用了 name,所以name不会被垃圾回收器回收。

4,闭包的特点和使用场景

3.1,特点

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

因为这2个特点,也就产生了下面的使用场景:

3.2,使用场景

避免全局变量污染

在 js 还无法模块化的时期,多人协作有可能会导致定义的全局变量产生命名冲突。使用闭包可以将变量和对应的功能放到一个独立的空间中,来在一定程度上解决全局变量污染问题。

const name = '全局' // 全局变量const init = (function () {const name = '局部name1'function callName() {console.log(name)}return function () {callName()}
})()
init() // 局部 name1const initSuper = (function () {const name = '局部name2'function callName() {console.log(name)}return function () {callName()}
})()
initSuper() // 局部 name2

函数柯里化

参考 这篇文章

5,闭包经典问题

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

上面的代码中,我们预期 1s 后输出 0,1,2,但实际会输出3个3。

这个问题就是因为闭包导致的。setTimeout 的回调函数访问了外部变量 i,形成闭包。而变量 i 只有1个,循环结束后,访问的变量 i 也是同一个。

解决

方式1:利用立即执行函数,实现 setTimeout 的回调函数不再访问外部变量。

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

方式2:利用 setTimeout 的第3个参数,实现 setTimeout 的回调函数不再访问外部变量。

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

方式3:利用 let 关键字产生的块级作用域,让每次循环都是新的变量i

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

注意是块级作用域的原因,此时并没有产生闭包。

在这里插入图片描述


以上。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【高级网络程序设计】Block1总结

这一个Block分为四个部分&#xff0c;第一部分是Introduction to Threads and Concurrency &#xff0c;第二部分是Interruptting and Terminating a Thread&#xff0c;第三部分是Keep Threads safety&#xff1a;the volatile variable and locks&#xff0c;第四部分是Beyon…

【算法系列篇】递归、搜索和回溯(四)

文章目录 前言什么是决策树1. 全排列1.1 题目要求1.2 做题思路1.3 代码实现 2. 子集2.1 题目要求2.2 做题思路2.3 代码实现 3. 找出所有子集的异或总和再求和3.1 题目要求3.2 做题思路3.3 代码实现 4. 全排列II4.1 题目要求4.2 做题思路4.3 代码实现 前言 前面我们通过几个题目…

提升研究效率,尽在EndNote 21 forMac/win!

在科研领域&#xff0c;文献管理是一项至关重要的任务。研究人员需要快速而准确地收集、整理和引用大量的文献资料&#xff0c;以支持他们的研究工作。而EndNote 21作为一款功能强大的文献管理软件&#xff0c;能够帮助研究人员高效地管理文献资源&#xff0c;提升研究工作的效…

【Linux基础】1. Linux 启动过程

文章目录 【 1. 内核的引导 】【 2. 运行init 】 运行级别 【 3. 系统初始化 】【 4. 建立终端 】【 5. 用户登录系统 】【 6. 图形模式与文字模式的切换方式 】【 7. Linux关机 】 Linux系统的启动过程分为 5个阶段&#xff1a; &#xff08;1&#xff09;内核的引导。 &#…

Java中线程状态的描述

多线程-基础方法的认识 截止目前线程的复习 Thread 类 创建Thread类的方法 继承Thread类,重写run方法实现Runnable接口,重写run方法使用匿名内部类继承Thread类,重写run方法使用匿名内部类实现Runnable接口,重写run方法使用Lambda表达式 run方法中的所有的代码是当前线程对…

第二百一十六回 分享一种更新页面数据的方法

文章目录 1. 概念介绍2. 实现方法2.1 实现思路2.2 实现方法3. 示例代码4. 内容总结我们在上一章回中介绍了"如何创建单例模式"相关的内容,本章回中将 分享一种更新页面数据的方法.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章回中介绍一种更新页…

测站坐标系统 -- 东北天(ENU)坐标系、站心坐标系

目录 一、测站坐标系的定义 二、测站坐标系与地心地固坐标系的转换 2.1地心地固坐标系转到测站坐标系 2.2测站坐标系转到地心地固坐标系 三、方位角和高度角的计算 一、测站坐标系的定义 测站坐标系统以观测站( 或地面上某一个观测点 ) 为中心建立坐标系统&#xff0c;将这…

SQL基础:记录的基本操作

在上一节中&#xff0c;我们进行了表的新建&#xff0c;这一节我们讲一下记录的增加、修改、删除、查询。 增加 增加即使用insert语句&#xff0c; INSERT INTO users (user_id, username, password, email) VALUES (2, jane_smith, pass456, janeexample.com);查看插入的数…

代码随想录第三十六天(一刷C语言)|背包问题理论基础分割等和子集

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、背包问题 题目&#xff1a;有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装…

Docker的安装及使用

目录 安装Docker 安装yum工具 更新本地镜像源 安装docker 启动docker 关闭防火墙 docker启动命令 配置镜像加速 docker的使用 拉取nginx 查看本地镜像 把镜像文件nginx导出成tar文件 查看是否导出成功 ​编辑 删除本地镜像nginx:latest 导入镜像文件nginx 拉取…

Java项目-瑞吉外卖项目优化Day1

创建新仓库 push项目 新建分支v1.0做优化 导入Redis相关配置 导入坐标。 实现配置类&#xff0c;重写序列化器&#xff0c;也可以直接用StringRedisTemplate。 application.xml配置&#xff1a; 实现缓存短信验证码 将手机号与验证码存进redis。 从redis中获取验证码&…

微信小程序长按图片识别二维码

设置show-menu-by-longpress"true"即可&#xff0c;长按图片后会弹出一个菜单&#xff0c;若图片中包含二维码或小程序码&#xff0c;菜单中会有响应入口 <image src"图片地址" show-menu-by-longpress"true"></image>官方说明

大语言模型(LLM)与 Jupyter 连接起来了!

现在&#xff0c;大语言模型&#xff08;LLM&#xff09;与 Jupyter 连接起来了&#xff01; 这主要归功于一个名叫 Jupyter AI 的项目&#xff0c;它是官方支持的 Project Jupyter 子项目。目前该项目已经完全开源&#xff0c;其连接的模型主要来自 AI21、Anthropic、AWS、Co…

专栏十六:bulk以及单细胞空转中的progeny通路分析

progeny本身有自己的R包,可以提取通路基因集信息,团队把他嵌入另一个R包decoupleR中完成富集分析。decoupleR自己有详细的针对bulk和scRNAseq的教程 简单安装一下 devtools::install_github(saezlab/OmnipathR) devtools::install_github("saezlab/progeny") Bio…

6 最大积水量

蛮力求解 #include <iostream> using namespace::std; using std::cout; using std::cin; int zdjsl(int n, int height[]) {int sum 0;int left_max[n];int right_max[n];left_max[0] height[0];right_max[n-1] height[n-1];for(int i1; i<n; i){left_max[i] m…

arcmap + oracle11g 迁移数据 报错 copyFeatures失败

原因排查&#xff1a; 1.通过这个界面&#xff0c;我们无法查到真正的原因&#xff0c; 2.将数据拷贝到我们自己的arcmap服务器中&#xff0c;采用 单个要素 导入&#xff0c;从result面板中查找原因&#xff1b; 从上面这个图中&#xff0c;看到关键信息 DBMS error ORA-016…

C++ STL——栈和队列(stack queue)

本节目标 1.stack的介绍和使用及其模拟实现 2.queue的介绍和使用及其模拟实现 3.priority_queue的介绍和使用及其模拟实现 4.容器适配器 1.stack的介绍和使用及其模拟实现 1.1 stack的介绍 stack的文档介绍 根据stack的文档介绍可以知道&#xff0c;stack是一种容器适配器…

关于“Python”的核心知识点整理大全29

目录 11.2.4 方法 setUp() 注意 11.3 小结 第二部分 项目1 外星人入侵 第&#xff11;2 章 武装飞船 注意 12.1 规划项目 12.2 安装 Pygame 注意 12.2.1 使用 pip 安装 Python 包 注意 如果你启动终端会话时使用的是命令python3&#xff0c;那么在这里应使用命令…

【python VS vba】(10) 在python使用matplotlib库来画不同的图形

7 下面是不同类型的图形 6 比如 散点图 sactter import numpy as np import matplotlib.pyplot as plt# 散点图 # x, y x np.random.normal(0, 1, 20) y np.random.normal(0, 1, 20)# 绘制散点图 plt.scatter(x, y, s25, alpha0.75)plt.xlabel("X") plt.ylabel(&…

Linux多版本cuda切换

目标 将cuda版本从10.0切换为11.1 步骤 查看当前cuda版本&#xff1a; nvcc -V编辑.bashrc文件&#xff1a; vim ~/.bashrc在文件中添加以下几行&#xff08;若已存在则忽略&#xff09;&#xff1a; export PATH$PATH:/usr/local/cuda/bin export LD_LIBRARY_PATH$LD_LI…