目录

 一、闭包是什么?概念

二、闭包为什么存在?作用

1. 创建私有变量

2. 实现数据封装与信息隐藏

3. 模拟私有方法

4. 保存函数执行时的状态

5. 回调函数和事件处理

6. 模块化编程

7. 懒加载与延迟执行

 三、闭包怎么用?实践+业务场景

1. 封装私有变量

2. 延迟执行(定时器、异步回调)

3. 事件监听和回调函数

5. 防抖和节流

业务场景:权限控制和角色管理

四、深入底层了解闭包的运行原理(难度指数⭐⭐⭐⭐)


 一、闭包是什么?概念


闭包是指 函数可以“记住”并访问定义时的作用域,即使这个函数在外部被调用时,依然能访问到其定义时的父函数的局部变量。

  • 父函数和子函数

    • 闭包通常发生在一个函数(父函数)内部定义了另一个函数(子函数),且子函数可以访问父函数的局部变量。
  • 通过return暴露子函数

    • 当父函数返回子函数时,子函数就形成了闭包。因为子函数不仅仅是返回的函数,它还“记住”了父函数的作用域。
  • 作用域链和内存管理

    • 通常,父函数的局部变量在父函数执行完毕后会被销毁,但由于闭包的存在,这些局部变量会被保留在内存中,直到闭包不再被引用。
    • 闭包使得父函数的局部变量不被销毁,同时也避免了全局作用域的污染,因为它们只在闭包内部可见。


二、闭包为什么存在?作用

1. 创建私有变量

闭包最常见的作用之一是实现 私有变量。在 JavaScript 中,变量通常是公开的,任何函数都能访问它们。而闭包允许我们创建只能通过特定函数访问的私有变量,这样就可以避免外部代码随意访问或修改它们。

  • 示例:
function createCounter() {let count = 0; // 这是一个私有变量return {increment: function() {count++;console.log(count);},decrement: function() {count--;console.log(count);},getCount: function() {return count;}};
}const counter = createCounter();
counter.increment(); // 输出: 1
counter.increment(); // 输出: 2
console.log(counter.getCount()); // 输出: 2
// count 变量是私有的,外部无法直接访问

在这个例子中,count 变量通过闭包被封装在 createCounter 函数中,外部无法直接访问和修改它,只有通过 incrementdecrement getCount 方法才能操作它。

2. 实现数据封装与信息隐藏

闭包提供了数据封装的能力,可以将状态和行为封装在一个函数内部,并通过暴露的接口与外部进行交互。这有助于信息隐藏,防止外部代码不小心或恶意地修改内部数据。

  • 示例:

function bankAccount(initialBalance) {let balance = initialBalance; // 私有变量return {deposit: function(amount) {balance += amount;console.log(`Deposited: $${amount}`);},withdraw: function(amount) {if (balance >= amount) {balance -= amount;console.log(`Withdrew: $${amount}`);} else {console.log('Insufficient funds');}},getBalance: function() {return balance;}};
}const myAccount = bankAccount(1000);
myAccount.deposit(500); // Deposited: $500
myAccount.withdraw(200); // Withdrew: $200
console.log(myAccount.getBalance()); // 1300
// 不能直接访问或修改 balance

这里的 balance 变量在 bankAccount 函数的作用域内被封装,外部无法直接访问或修改它,只有通过 depositwithdraw getBalance 方法才能与其交互。

3. 模拟私有方法

除了私有变量,闭包也可以用来模拟 私有方法。你可以将某些功能封装在闭包内部,外部只能通过公开的方法调用它们,从而达到隐藏细节、减少外部依赖的目的。

  • 示例:

function car(model) {let speed = 0; // 私有变量function accelerate() {speed += 10;console.log(`Accelerating... Speed is now ${speed} km/h`);}return {start: function() {console.log(`${model} is starting`);accelerate();}};
}const myCar = car('Toyota');
myCar.start(); // Toyota is starting// Accelerating... Speed is now 10 km/h

在这个例子中,accelerate 函数是私有的,外部无法直接调用它,只有通过 start 方法间接调用。

4. 保存函数执行时的状态

闭包能够保持其外部函数的执行上下文,即使外部函数已经执行完毕。这样,我们可以保存函数的 状态,在后续的调用中继续使用这些状态。这对于处理 异步操作回调函数 中的状态非常有用。

  • 示例:
function makeAdder(x) {return function(y) {return x + y; // 闭包可以记住 x 的值};
}const add5 = makeAdder(5);
console.log(add5(10)); // 15
const add10 = makeAdder(10);
console.log(add10(10)); // 20

在这个例子中,makeAdder 返回的函数是一个闭包,它“记住”了 x 的值。即使 makeAdder 执行结束后,x 仍然在闭包中保存,并且在后续的调用中可以使用它。

5. 回调函数和事件处理

在前端开发中,闭包广泛应用于 事件处理异步回调。它们能够保持对外部数据(如事件触发时的状态、函数参数等)的访问,即使在异步操作完成后,闭包仍然能够访问这些数据。

  • 示例:事件处理中的闭包
function setupButton() {let counter = 0; // 闭包中的私有状态document.getElementById('myButton').addEventListener('click', function() {counter++;console.log(`Button clicked ${counter} times`);});
}setupButton();

在这个例子中,事件回调函数可以访问 counter 变量,它即使在 setupButton 函数执行完毕后仍然保持状态。

6. 模块化编程

闭包帮助我们将代码分成独立的模块,每个模块有自己的私有数据和方法。这样不仅可以避免全局命名冲突,还可以提高代码的可维护性和可复用性。

  • 示例:
const counterModule = (function() {let count = 0; // 私有变量return {increment: function() {count++;console.log(count);},decrement: function() {count--;console.log(count);}};
})();counterModule.increment(); // 1
counterModule.decrement(); // 0

通过立即执行函数表达式(IIFE),counterModule 模块中的 count 是私有的,外部无法直接访问。闭包保证了每个模块都有独立的作用域和私有数据。

7. 懒加载与延迟执行

闭包还可以用于延迟执行函数和延迟计算,常见于懒加载场景。例如,某些数据或资源的加载操作可以通过闭包延迟到需要时再执行。

  • 示例:

function fetchData() {let data = null;return function() {if (data === null) {console.log('Fetching data...');data = 'Some data'; // 模拟数据加载}return data;};
}const getData = fetchData();
console.log(getData()); // Fetching data... Some data
console.log(getData()); // Some data

这里,data 只在第一次调用 getData() 时被加载,之后就不会再进行加载操作,闭包保存了 data 的状态。

 三、闭包怎么用?实践+业务场景

1. 封装私有变量

闭包常常用于封装私有变量和创建数据的封装(即模块化编程)。在 JavaScript 中,通常没有内建的私有变量机制,但闭包可以帮助你达到类似的效果。

  • 示例:计数器
function createCounter() {let count = 0;  // 私有变量return {increment: function() {count++;console.log(count);},decrement: function() {count--;console.log(count);},getCount: function() {return count;}};
}const counter = createCounter();
counter.increment();  // 输出: 1
counter.increment();  // 输出: 2
counter.decrement();  // 输出: 1
console.log(counter.getCount());  // 输出: 1

解析

  • count 是一个私有变量,只能通过 incrementdecrement getCount 方法访问。
  • 外部无法直接访问 count,实现了数据的封装。

2. 延迟执行(定时器、异步回调)

闭包经常用于处理异步操作和定时任务。例如,使用 setTimeout setInterval 时,闭包允许你保留函数的执行上下文,从而延迟执行某些操作。

  • 示例:延迟执行任务

function createDelayedTask(message, delay) {return function() {setTimeout(function() {console.log(message);}, delay);};
}const delayedTask = createDelayedTask('Hello, World!', 2000);
delayedTask();  // 2秒后输出: Hello, World!

解析

  • createDelayedTask 返回一个闭包,这个闭包可以记住其外部环境中的变量(如 message delay)。
  • 通过 setTimeout 延迟输出 message,即使函数 createDelayedTask 已经执行完毕。

3. 事件监听和回调函数

闭包在事件监听器和回调函数中非常常见。它可以让回调函数访问外部作用域中的变量,从而保持对数据的引用。

5. 防抖和节流

防抖和节流是常见的性能优化技巧。防抖(Debouncing)通常用于限制某些操作频繁触发(如输入框中的搜索建议),而节流(Throttling)则是控制某些操作的触发频率(如窗口大小调整事件)。

业务场景:权限控制和角色管理

闭包可以用于权限管理和角色管理的场景中,通过闭包来封装不同角色的权限信息,从而提供灵活的权限控制。

  • 示例:权限管理
function createRoleChecker(role) {const permissions = {admin: ['read', 'write', 'delete'],user: ['read'],guest: []};return function(permission) {if (permissions[role] && permissions[role].includes(permission)) {console.log(`${role} has ${permission} permission.`);} else {console.log(`${role} does not have ${permission} permission.`);}};
}const adminChecker = createRoleChecker('admin');
const userChecker = createRoleChecker('user');
const guestChecker = createRoleChecker('guest');adminChecker('write');  // admin has write permission.
userChecker('write');   // user does not have write permission.
guestChecker('read');   // guest does not have read permission.

解释

  • createRoleChecker 返回一个闭包,它保存了角色的权限信息。
  • 每次调用 roleChecker 时,可以判断特定角色是否拥有某个权限。
  • 通过闭包,你可以灵活地管理角色和权限数据,避免权限数据暴露。

四、深入底层了解闭包的运行原理(难度指数⭐⭐⭐⭐)

思考:

  • 下面代码输出什么?
  • A(2) 执行时,局部变量 x y 存储在内存中,它们什么时候会被销毁
function A(y) {let x = 2;function B(z) {console.log(x + y + z);}return B;
}let C = A(2);
C(3);

上述执行的过程中到底在做什么:

  1. A(2) 的调用:

    • JavaScript 引擎会为 A(2) 创建一个 执行上下文。
    • 当调用 A(2) 时,y 赋值为 2,并且在 A 内部创建了一个局部变量 x = 2 和一个函数 B(z)
    • 然后,A 返回了函数 B
  2. 形成闭包:

    • 函数 BA 内部定义,因此它形成了闭包,能够访问 A 内部的变量 xy,即使 A 执行完毕,B 仍然可以访问这些变量。
    • A(2) 执行完,JavaScript 会销毁 A 的执行上下文,但由于 B 是通过闭包持有对 A 作用域的引用,因此 x y 并没有被销毁,它们的内存空间会保留下来。
  3. B 赋值给 C:

    • 通过 let C = A(2);,变量 C 被赋值为函数 B,且 C 具有 A 中的作用域(闭包),能够访问 xy
  4. 调用 C(3):

    • 当调用 C(3) 时,实际执行的是 B(3)。JavaScript 会创建C(3) 的执行上下文:即 B(3) 的执行上下文。
    • B 中,x y 来自 A 的作用域,z 来自 B 的参数。因此,x + y + z 被计算为 7,并打印出来。
    • C(3) 调用结束,C(3) 的执行上下文 B(3) 的执行上下文会被销毁。 但闭包仍然存在,因为 B 被保存在变量 C 中,并且 C 仍然引用着闭包。当 C B 被垃圾回收时,闭包才会被销毁。因此,在 C(3) 执行后,虽然 B 的执行上下文栈帧被销毁,但闭包中的内存(如 x y)会继续存在,直到 C 不再引用 B

参考【易混概念】执行上下文和内存空间的联系区别

留作业:如果闭包 B 被赋值给多个其他变量,这些变量会如何影响 x y 的内存空间

评论区做答。

订阅《前端通过之路》,助你一路通关!

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

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

相关文章

算法学习笔记:19.牛顿迭代法——从原理到实战,涵盖 LeetCode 与考研 408 例题

牛顿迭代法(Newtons Method)是一种强大的数值计算方法,由英国数学家艾萨克・牛顿提出。它通过不断迭代逼近方程的根,具有收敛速度快、适用范围广的特点,在科学计算、工程模拟、计算机图形学等领域有着广泛应用。牛顿迭…

小白学Python,操作文件和文件夹

目录 前言 一、操作文件路径 1.获取当前路径 2.创建文件夹 (1)mkdir()函数 (2)makedirs() 函数 3.拼接路径 4.跳转路径 5.判断相对路径和绝对路径 6.获取文件路径和文件名 二、操作文件和文件夹 1.查询文件大小 2.删除…

015_引用功能与信息溯源

引用功能与信息溯源 目录 引用功能概述支持的模型引用类型API使用方法引用格式应用场景最佳实践 引用功能概述 什么是引用功能 Claude的引用功能允许在回答基于文档的问题时提供详细的信息来源引用,帮助用户追踪和验证信息的准确性。这个功能特别适用于需要高可…

ROS2中的QoS(Quality of Service)详解

ROS2中的QoS(Quality of Service)详解1. 主要QoS参数2. 为什么需要设置QoS3. QoS兼容性规则4. 选择QoS策略的建议5. 调试QoS问题的方法6. 踩坑:订阅话题没有输出可能的原因:调试方法QoS是ROS2中用于控制通信质量和行为的机制。它定…

Cursor三大核心AI功能

一:Tab键:智能小助手 1.1 单行/多行代码补全 在代码中写出要实现的功能,第一次按Tab生成代码,第二次按Tab接受代码。1.2 智能代码重写 对已有代码重新编写。 写个注释告诉AI重构方法,然后鼠标点到方法内部,…

cesium添加原生MVT矢量瓦片方案

项目中需要基于cesium接入mvt格式的服务并支持属性拾取查询,通过一系列预研测试,最后选择cesium-mvt-imagery-provider开源插件完成,关键源码信息如下: npm i cesium cesium-mvt-imagery-provider //安装依赖包// 加载图层import…

AI金融风控:识别欺诈,量化风险的新利器

AI金融风控:识别欺诈,量化风险的新利器深度学习算法穿透海量交易数据,92.5%的不良贷款识别率宣告了金融风险防控新时代的来临。深圳桑达银络科技有限公司在2025年6月申请的“基于人工智能的金融交易反欺诈系统”专利,揭示了金融风…

【unitrix】 5.0 第二套类型级二进制数基本结构体(types2.rs)

一、源码 这是一个使用 Rust 类型系统实现类型级(type-level)二进制数的设计。 //! 类型级二进制数表示方案(第二套方案) //! //! 使用嵌套泛型结构体表示二进制数,支持整数和小数表示。use crate::sealed::Sealed;/// 类型级二进制数结构体 …

DAY01:【ML 第一弹】机器学习概述

一、三大概念 1.1 人工智能(AI) Artificial Intelligence 人工智能AI is the field that studies the synthesis and analysis of computational agents that act intelligently 1.2 机器学习(ML) Machine Learning 机器学习Fi…

AGX Xavier 搭建360环视教程【一、先确认方案】

设备默认自带 NVIDIA 硬件编解码能力(NVDEC/NVENC),但是需要你在 OpenCV 和 FFmpeg 里正确启用 调通 GStreamer 或 nvmpi,才真正能用起来!这里的硬解码是核心:Jetson 平台的硬解码,要么走 GStr…

服务器怎么跑Python项目?

在服务器上运行 Python 项目通常涉及 环境配置、依赖安装、项目部署 和 进程管理。以下是详细步骤:1. 连接服务器确保你能通过 SSH 访问服务器:ssh usernameyour_server_ip(如果是本地测试,可跳过这一步)2. 安装 Pytho…

【软件设计师】

UML 类图中的关系用例图中的关系 关系例子类图用例图顺序图 概念示例通信图活动图泳道图状态图

Java 内部类详解:从基础到实战,掌握嵌套类、匿名类与局部类的使用技巧

作为一名 Java 开发工程师,你一定在实际开发中遇到过这样的场景:想在一个类内部定义另一个逻辑相关的类;需要为某个接口或抽象类提供一个临时实现(比如监听器);想利用面向对象特性来组织代码结构&#xff0…

Java设计模式之行为型模式(观察者模式)介绍与说明

一、模式结构 观察者模式包含以下四个角色: Subject(主题/被观察者) 维护观察者列表,提供注册(registerObserver)、移除(removeObserver)观察者的方法,并定义通知所有观察…

实现一个点击输入框可以弹出的数字软键盘控件 qt 5.12

我们将创建两个自定义组件: 1. NumericInputField:一个输入框,当点击时弹出数字键盘。 2. NumericKeyboard:一个可缩放的数字键盘。 设计思路: - NumericInputField 是一个常规的输入框,但点击后会弹出 Num…

Java 深入解析:JVM对象创建与内存机制全景图

第一章:引言 Java 是一种面向对象的编程语言,对象(Object)是其最基本的组成单位。Java 的“一切皆对象”不仅体现在语法层面,更体现在运行时,几乎所有数据都以对象形式存在于内存中。 然而,很…

Redis 基本操作笔记

1. Redis 简介 Redis(Remote Dictionary Server)是一个开源的、高性能的键值对存储系统,通常作为数据库、缓存、消息中间件等使用。它支持多种数据类型,包括字符串、哈希、列表、集合、有序集合等。 Redis 特点: 性能&…

Docker从环境配置到应用上云的极简路径

Docker从环境配置到应用上云的极简路径主要包括环境配置、应用容器化、选择云平台及部署应用等步骤,具体如下: - 配置Docker环境: - 安装Docker:根据操作系统下载对应版本的Docker安装包。如在Linux系统中,可使用命令…

Slicer渲染Dicom到nrrd

Slicer渲染Dicom到nrrd 工作中遇到一些处理Dicom数据的需求,个人通过网络上的一些教程 对于原始数据尝试转换到nrrd时,发现部分的窗体数据的渲染方向不一致 进一步发现这些很多定义的方向是跟设备厂家强相关的,不同厂家对于同一段的Dicom参…

QT中设计qss字体样式但是没有用【已解决】

检查一下stylesheet里面是不是有不能被QT读取的CSS语言,可能会跟字体颜色冲突错误示范:/* 错误示例:QSS 中使用 box-shadow */ QPushButton {box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); /* Qt 不支持此属性 */ }删掉就行了如果后续想用阴影…