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;内核的引导。 &#…

docker nginx 部署静态网站

1、dockerfile FROM nginx AS baseWORKDIR /appEXPOSE 80COPY . /app2、dockercompose.yaml version: 3 services:adminservice:container_name: adminwebbuild:context: ./dockerfile: Dockerfileports:- "5000:80"labels:description: adminwebrestart: always3、…

Java中线程状态的描述

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

ubuntu 20.04 docker

ubuntu 20.04 docker https://docs.docker.com/engine/install/ubuntu/ Ubuntu20.04下部署linux资源监控平台&#xff08;docker部署&#xff09;grafanaprometheusnode_exporter&#xff08;docker离线包&#xff09; https://blog.csdn.net/deer_cui/article/details/1340208…

React面试题:对componentWillReceiveProps的理解

React面试题&#xff1a;对componentWillReceiveProps的理解 回答思路&#xff1a;是什么--->干什么用的-->优点-->什么时候用是什么&#xff1f;干什么用的&#xff1f;优点什么时候用&#xff1f; 回答思路&#xff1a;是什么—>干什么用的–>优点–>什么时…

已知IP地址,求能容纳一定数量IP地址的DHCP服务器的主机数量和子网掩码

求能容纳的主机数量&#xff1a;2的n次方-2 > 所需的主机数量 [由于广播地址&#xff08;255&#xff09;和网络地址&#xff08;0&#xff09;要保留&#xff0c;所以求主机数量时要-2] 子网掩码&#xff1a;采用1 2 4 8法&#xff08;2的0次方、2的1次方、2的2次方、2的3次…

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

文章目录 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;求解将哪些物品装…

C语言实现顺序队列

在C语言中&#xff0c;顺序队列是一种数据结构&#xff0c;它是一种先进先出&#xff08;FIFO&#xff09;的线性表。顺序队列通常使用数组来实现&#xff0c;具有以下特点&#xff1a; 队列元素的插入操作&#xff08;入队&#xff09;只能在队尾进行&#xff0c;而删除操作&…

Docker的安装及使用

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

Unity工具栏介绍

File 在Unity的工具栏中&#xff0c;File&#xff08;文件&#xff09;选项提供了一些重要的功能&#xff0c;使你能够管理项目和资源。以下是File选项中常见的功能&#xff1a; 1. New Project&#xff08;新建项目&#xff09;&#xff1a; 创建一个新的Unity项目。你可以…

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>官方说明

spring 配置模型

一、引言 本文将会介绍spring的配置模型、配置初始化和动态刷新。 二、技术细节 1、配置模型 Environment ->Profile -> active / defaultMutablePropertySources -> PropertySourcer -> servlet,system,springPropertyResolver -> PropertyConvensionMutabl…

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

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