闭包的两种设计模式

闭包设计模式

概述

闭包是 JavaScript 中的一个重要概念,它允许内层函数访问外层函数的变量。在实际开发中,闭包经常被用于实现特定的设计模式,主要包括辅助函数模式工厂模式

1. 辅助函数模式(Helper Function Pattern)

基本结构

function mainFunction(params) {// 外层变量:状态存储let sharedState = initialValue;function helperFunction(helperParams) {// 内层函数:核心逻辑实现// 可以访问和修改 sharedState}// 调用辅助函数helperFunction(params);return sharedState;  // 返回处理结果
}

特点分析

  • 目的:代码组织和私有化
  • 生命周期:单次调用期间
  • 状态管理:临时状态,每次调用重新初始化
  • 访问权限:辅助函数无法被外部直接访问
  • 使用场景:递归算法、复杂逻辑分解

实际应用示例

1. 数组扁平化
function flatArr(list) {let result = [];function flat(currentList) {  // 私有辅助函数for (let i = 0; i < currentList.length; i++) {if (!Array.isArray(currentList[i])) {result.push(currentList[i]);} else {flat(currentList[i]);  // 递归调用}}}flat(list);return result;
}// 使用示例
console.log(flatArr([1, [2, 3], [4, [5, 6]]])); // [1, 2, 3, 4, 5, 6]
2. 深度优先搜索
function dfsCollect(tree) {let result = [];function dfs(node) {  // 私有辅助函数if (!node) return;result.push(node.value);if (node.children) {node.children.forEach(dfs);}}dfs(tree);return result;
}
3. 对象深拷贝
function deepClone(obj) {let visited = new WeakMap();  // 解决循环引用function clone(current) {  // 私有辅助函数if (current === null || typeof current !== 'object') {return current;}if (visited.has(current)) {return visited.get(current);}const result = Array.isArray(current) ? [] : {};visited.set(current, result);for (let key in current) {if (current.hasOwnProperty(key)) {result[key] = clone(current[key]);}}return result;}return clone(obj);
}

2. 工厂模式 + 装饰器模式(Factory + Decorator Pattern)

基本结构

function createEnhancedFunction(originalFunction) {// 外层变量:持久状态const persistentState = {};return function enhancedFunction(...args) {// 增强逻辑:使用持久状态提供额外功能// 调用原函数并返回结果return originalFunction(...args);};
}

特点分析

  • 目的:增强函数功能,状态管理
  • 生命周期:长期存在,跨多次调用
  • 状态管理:持久状态,每个实例独立维护
  • 访问权限:返回的函数可以被外部调用
  • 使用场景:缓存、节流、防抖、日志记录

实际应用示例

1. 函数缓存(记忆化)
function createCache(fn) {const cache = new Map();  // 持久缓存状态return function (...args) {  // 增强的函数const key = JSON.stringify(args);if (cache.has(key)) {console.log(`Cache hit for args: ${key}`);return cache.get(key);}console.log(`Cache miss for args: ${key}`);const result = fn(...args);cache.set(key, result);return result;};
}// 使用示例
const fibonacci = createCache(function(n) {if (n <= 1) return n;return fibonacci(n - 1) + fibonacci(n - 2);
});console.log(fibonacci(10)); // 计算并缓存
console.log(fibonacci(10)); // 从缓存获取
2. 函数节流
function createThrottle(delay) {let lastTime = 0;  // 持久时间状态return function(fn) {  // 增强的函数return function(...args) {const now = Date.now();if (now - lastTime >= delay) {lastTime = now;return fn.apply(this, args);}};};
}// 使用示例
const throttle = createThrottle(1000);
const throttledLog = throttle(console.log);
3. 函数调用计数器
function createCounter(fn) {let count = 0;  // 持久计数状态return function(...args) {  // 增强的函数count++;console.log(`函数被调用了 ${count}`);return fn.apply(this, args);};
}// 使用示例
const countedAdd = createCounter((a, b) => a + b);
console.log(countedAdd(1, 2)); // 函数被调用了 1 次 \n 3
console.log(countedAdd(3, 4)); // 函数被调用了 2 次 \n 7
4. 函数执行时间统计
function createTimer(fn) {const stats = { totalTime: 0, callCount: 0 };  // 持久统计状态return function(...args) {  // 增强的函数const start = Date.now();const result = fn.apply(this, args);const end = Date.now();stats.totalTime += (end - start);stats.callCount++;stats.averageTime = stats.totalTime / stats.callCount;console.log(`执行时间: ${end - start}ms, 平均时间: ${stats.averageTime}ms`);return result;};
}

3. 两种模式对比

特性辅助函数模式工厂模式 + 装饰器模式
主要目的代码组织和逻辑分解功能增强和状态管理
状态生命周期单次调用期间跨多次调用持久存在
状态初始化每次调用重新初始化创建时初始化,后续复用
函数可见性辅助函数私有返回增强后的公开函数
使用方式直接调用主函数先创建增强函数,再调用
典型应用递归算法、复杂计算缓存、节流、装饰器

4. 选择指南

使用辅助函数模式的场景:

  • 需要将复杂逻辑分解为多个步骤
  • 实现递归算法时需要维护中间状态
  • 希望隐藏实现细节,只暴露主要接口
  • 需要在单次执行中共享临时变量

使用工厂模式 + 装饰器模式的场景:

  • 需要为现有函数添加额外功能
  • 需要维护跨调用的持久状态
  • 实现缓存、节流、防抖等功能增强
  • 需要创建多个具有相似功能但独立状态的函数

5. 常见问题解答(FAQ)

Q: 多个 createCache 实例会造成缓存冲突吗?

A: 不会冲突。每次调用 createCache(fn) 都会创建一个全新的闭包环境,每个返回的函数都有自己独立的 cache 变量。

原理解释
function createCache(fn) {const cache = new Map();  // 每次调用createCache都会创建新的cachereturn function (...args) {// 这个返回的函数只能访问它所属闭包环境中的cacheconst key = JSON.stringify(args);if (cache.has(key)) {console.log(`Cache hit for ${fn.name}: ${key}`);return cache.get(key);}console.log(`Cache miss for ${fn.name}: ${key}`);const result = fn(...args);cache.set(key, result);return result;};
}
实际验证示例
// 创建两个不同的缓存函数
const cachedAdd = createCache(function add(a, b) {return a + b;
});const cachedMultiply = createCache(function multiply(a, b) {return a * b;
});// 测试:相同参数但不同函数
console.log('=== 第一次调用 ===');
console.log(cachedAdd(2, 3));      // Cache miss for add: [2,3] -> 5
console.log(cachedMultiply(2, 3));  // Cache miss for multiply: [2,3] -> 6console.log('=== 第二次调用相同参数 ===');
console.log(cachedAdd(2, 3));      // Cache hit for add: [2,3] -> 5
console.log(cachedMultiply(2, 3));  // Cache hit for multiply: [2,3] -> 6// 结果:每个函数都有自己独立的缓存,互不影响
闭包作用域独立性
// 每次调用 createCache 时的内存模型:
const fn1 = createCache(addFunction);    // 创建闭包环境1,有独立的cache1
const fn2 = createCache(multiplyFunction); // 创建闭包环境2,有独立的cache2// 内存结构示意:
/*
闭包环境1: {cache: Map { [2,3] => 5 }  // 独立的缓存空间1return function1
}闭包环境2: {cache: Map { [2,3] => 6 }  // 独立的缓存空间2return function2
}
*/// fn1 只能访问 cache1
// fn2 只能访问 cache2
// 它们完全独立,互不影响
需要注意的场景
// 同一个函数创建多个缓存实例
const fibonacci = function(n) {if (n <= 1) return n;return fibonacci(n - 1) + fibonacci(n - 2);
};const cache1 = createCache(fibonacci);
const cache2 = createCache(fibonacci);// 这样会创建两个独立的缓存,可能造成重复计算
console.log(cache1(10)); // 第一个缓存计算
console.log(cache2(10)); // 第二个缓存重新计算(因为缓存是独立的)
最佳实践
// 1. 单例模式:对于同一个函数,通常只创建一个缓存实例
const cachedFibonacci = createCache(function fibonacci(n) {if (n <= 1) return n;return cachedFibonacci(n - 1) + cachedFibonacci(n - 2);
});// 2. 带清理功能的缓存
function createCacheWithClear(fn) {const cache = new Map();const cachedFn = function (...args) {const key = JSON.stringify(args);if (cache.has(key)) {return cache.get(key);}const result = fn(...args);cache.set(key, result);return result;};// 添加清理方法cachedFn.clearCache = () => cache.clear();cachedFn.getCacheSize = () => cache.size;return cachedFn;
}// 使用示例
const smartCache = createCacheWithClear((x) => x * x);
console.log(smartCache(5)); // 25
console.log(smartCache.getCacheSize()); // 1
smartCache.clearCache();
console.log(smartCache.getCacheSize()); // 0

6. 最佳实践

性能考虑

  • 辅助函数模式:避免在频繁调用的函数中使用复杂的辅助逻辑
  • 工厂模式:注意内存泄漏,合理管理持久状态的生命周期

可读性优化

  • 使用描述性的函数名称
  • 添加适当的注释说明闭包的用途
  • 保持函数职责单一

调试技巧

  • 在开发环境中添加日志输出
  • 使用浏览器开发者工具的断点调试
  • 注意闭包可能导致的内存占用

总结

闭包设计模式是 JavaScript 中强大的编程工具,通过合理运用辅助函数模式和工厂模式,可以写出更加优雅、可维护的代码。关键是要根据具体的使用场景选择合适的模式,并注意性能和内存管理的最佳实践。

核心要点:

  • 每个闭包创建独立的作用域环境
  • 工厂函数返回的每个实例都有独立的状态
  • 合理使用闭包可以实现强大的功能增强
  • 注意内存管理和性能优化

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

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

相关文章

力扣119:杨辉三角Ⅱ

力扣119:杨辉三角Ⅱ题目思路代码题目 给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 思路 杨辉三角的规律&#xff1a; 第n行有n1个数&#xff0c;n从0开始每行的第一个数和最后一个数都是1。除了第一行的数其他的数都是由它左上角和右上角的…

@Linux自建证书 Nginx+HTTPS

文章目录Linux自建证书并配置Nginx HTTPS1. 准备工作2. 安装必要的工具3. 自建证书脚本3.1 创建CA根证书&#xff08;可选&#xff0c;用于自签名证书链&#xff09;3.2 创建服务器证书脚本4. 证书文件说明5. 配置Nginx使用HTTPS5.1 创建证书存放目录5.2 配置Nginx站点5.3 启用…

解决 RAGFlow报错 `peewee.OperationalError: (1045)`:MySQL 容器密码错误排查手记

文章目录 🕵️‍♂️ 深度排查过程 🔧 完美解决方案:同步真实密码 ✅ 验证结果 📌 经验总结与最佳实践 问题现象: 部署 ragflow 时遭遇以下错误,且访问前端页面无响应: peewee.OperationalError: (1045, "Access denied for user root@192.168.48.6")关键线…

lwIP WebSocket 客户端 TCP PCB 泄漏问题分析与解决

在嵌入式开发中&#xff0c;使用 lwIP 实现 WebSocket 客户端时&#xff0c;偶尔会遇到反复连接导致 TCP PCB&#xff08;Protocol Control Block&#xff09;泄漏&#xff0c;最终连接数达到上限&#xff08;如 4&#xff09;后无法再建立新连接的问题。本文将结合实际案例&am…

k8s之Attach 和 Mount

Attach 和 Mount 一、核心概念对比操作Attach&#xff08;挂载设备&#xff09;Mount&#xff08;挂载文件系统&#xff09;定义将存储卷&#xff08;如 EBS、NFS 等&#xff09;连接到宿主机将已 Attach 的存储设备映射为宿主机上的文件系统路径执行者云提供商驱动&#xff08…

API Gateway HTTP API 控制客户端访问 IP 源

前言 在 API Gateway REST API 中我们可以配置 Resource policy 来实现对特定客户端 IP 地址的限制. 然而 HTTP API 并不提供这个功能, 不过我们可以用 Lambda 搓一个 Authorizer 实现等效的功能. 创建 Lambda authorizer import json import os import ipaddressdef lambda…

Linux搭建LAMP环境(CentOS 7 与 Ubuntu 双系统教程)

Linux搭建LAMP环境 一、LAMP 环境核心概念 定义&#xff1a;由 Linux、Apache、MySQL、PHP 四大组件组成的开源 Web 应用平台本质&#xff1a;四个独立开源软件的组合体&#xff0c;因长期协同使用形成高度兼容性&#xff0c;成为动态网站和服务器的主流解决方案 二、LAMP 四大…

c# 开机自动启动程序

以下是两种实现C#软件开机自启动的常用方法&#xff0c;根据需求选择适合的方案&#xff1a; 方法1&#xff1a;通过注册表实现&#xff08;需管理员权限&#xff09; using Microsoft.Win32; using System.Diagnostics;public static class AutoStartManager {/// <summa…

C语言---动态内存管理

为什么要有动态内存分配我们在学习动态内存管理之前&#xff0c;一直都是通过开辟变量&#xff0c;或者是开辟数组的方式来在内存的栈区开辟空间的&#xff0c;但是这样的开辟方式有局限性&#xff0c;因为一旦开辟之后&#xff0c;它们的大小就无法改变&#xff0c;就缺少了很…

C++标准库(std)详解

C标准库&#xff08;std&#xff09;详解——目录C标准库&#xff08;std&#xff09;详解一、命名空间&#xff08;namespace&#xff09;二、主要组件1. 输入输出流&#xff08;<iostream>&#xff09;2. 字符串处理&#xff08;<string>&#xff09;3. STL容器&…

ESP32的OTA升级详解:3. 搭建node/python服务器升级(native ota原生API)

一、OTA两种方式&#xff1a;app_update 与 esp_https_ota 区别 ESP32/ESP32-S2/ESP32-C3等可通过Wi-Fi或以太网下载新固件到OTA分区实现运行时升级。ESP-IDF提供两种OTA升级方法&#xff1a; 使用app_update组件的原生API使用esp_https_ota组件的简化API(支持HTTPS升级) 本次…

byte[]作为接口参数传递的方法

在C#中通过WebService或API传递byte参数&#xff08;如文件、图像等二进制数据&#xff09;时&#xff0c;通常有以下几种实现方式&#xff1a; ‌1. 使用Base64编码&#xff08;推荐REST API&#xff09;‌ 将byte数组转换为Base64字符串传输&#xff0c;适用于JSON格式的API&…

元宇宙与Web3的深度融合:构建沉浸式数字体验的愿景与挑战

一、技术特征与融合基础1. 元宇宙的技术架构&#xff08;2025年&#xff09;空间构建技术&#xff1a;3D建模与渲染&#xff1a;实时渲染引擎&#xff08;如Unity HDRP&#xff09;支持路径追踪光追&#xff0c;AI生成模型&#xff08;NVIDIA Get3D&#xff09;3秒生成3D场景。…

什么是脑裂

脑裂定义&#xff1a; 脑裂是分布式系统中由于网络分区&#xff08;Network Partition&#xff09;导致集群节点被分割成多个独立子集&#xff0c;每个子集认为自己是唯一合法的集群&#xff0c;从而导致数据不一致或系统行为异常的现象。详细工作原理&#xff1a;发生原因&…

川翔云电脑:云端算力新标杆,创作自由无边界

一、旗舰机型&#xff1a;4090Ultra 48G 显存颠覆硬件限制 川翔云电脑最新上线的RTX 4090Ultra 48G 显存机型&#xff0c;采用 NVIDIA Ada Lovelace 架构&#xff0c;单卡显存容量达 48GB GDDR6X&#xff0c;较传统 4090 翻倍&#xff0c;可直接加载 1200 万面数的超复杂模型&a…

贪心算法(排序)

码蹄集OJ-活动安排 #include<bits/stdc.h> using namespace std; struct MOOE {int s,e; }; bool compare(const MOOE&a,const MOOE&b) {return a.e<b.e; } int main( ) {int n;cin>>n;vector<MOOE>a(n);for(int i0;i<n;i){cin>>a[i].…

详解序数回归损失函数ordinal_regression_loss:原理与实现

在医疗 AI 领域&#xff0c;很多分类任务具有有序类别的特性&#xff0c;如疾病严重程度&#xff08;轻度→中度→重度&#xff09;、肿瘤分级&#xff08;G1→G2→G3&#xff09;等。这类任务被称为序数回归&#xff08;Ordinal Regression&#xff09;&#xff0c;需要特殊的…

SQL增查

建完库与建完表后后:1.分别查询student表和score表的所有记录student表&#xff1a;score表:2.查询student表的第2条到5条记录SELECT * FROM student LIMIT 1,4;3.从student表中查询计算机系和英语系的学生的信息SELECT * FROM student-> WHERE department IN (计算机系, 英…

二分答案之最大化最小值

参考资料来源灵神在力扣所发的题单&#xff0c;仅供分享学习笔记和记录&#xff0c;无商业用途。 核心思路&#xff1a;本质上是求最大 应用场景&#xff1a;在满足条件的最小值区间内使最大化 检查函数&#xff1a;保证数据都要大于等于答案 补充&#xff1a;为什么需要满…

OCR 赋能档案数字化:让沉睡的档案 “活” 起来

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;企业产品档案包含设计图纸、检测报告、生产记录等&#xff0c;传统数字化仅靠扫描存档&#xff0c;后续检索需人工逐份翻阅&#xff0c;效率极低。​OCR 产品档案解决方案直击痛点&#xff1a;通过智能识别技…