Javascript中如何实现函数缓存?函数缓存有哪些应用场景?
今天要聊的一个很经典的问题——如何在JavaScript中实现函数缓存,以及它有哪些应用场景。
我们先来明确一下,函数缓存是什么。简单来说,函数缓存是将函数的运算结果存储起来,以便下次用到相同的输入时,可以直接返回结果,而不需要重新计算。这种方式可以理解为用空间换时间,也就是用缓存存储的空间,换取未来计算时间的节省。
先给个简单的代码例子让大家感受一下:
const add = (a, b) => a + b;
const calc = memoize(add); // 创建一个支持缓存的函数
console.log(calc(10, 20)); // 输出 30
console.log(calc(10, 20)); // 直接从缓存中获取结果,输出 30
是不是很有意思?在这个例子中,calc函数并没有重复计算,而是利用缓存存储的结果直接返回。这种思路在性能优化中非常常见。接下来,我们来详细看看如何实现它。
函数缓存的实现,离不开JavaScript的几个核心概念:闭包、柯里化和高阶函数。
首先说说闭包。闭包的本质是函数加上它能够访问的变量总和。闭包允许我们在一个函数的作用域中保持变量的引用。比如:
function outer() {let a = 1;return function inner() {console.log(a); // 访问外部作用域的变量 a};
}const func = outer();
func(); // 输出 1
在这里,inner函数形成了闭包,它能够访问到外部函数outer中的变量a。闭包为实现函数缓存提供了重要的基础——我们可以利用它保存计算结果。
接下来说柯里化。柯里化是一种把接收多个参数的函数转换为接收一个参数的函数的技巧。比如:
function add(x) {return function (y) {return x + y;};
}console.log(add(3)(4)); // 输出 7
柯里化的好处是,我们可以延迟计算,或者分步计算,这对缓存机制的实现很有帮助。
最后是高阶函数。高阶函数是那些接收函数作为参数,或者返回一个函数的函数。比如:
function withLogging(func) {return function (...args) {console.log(`Calling with ${args}`);return func(...args);};
}const loggedAdd = withLogging((x, y) => x + y);
console.log(loggedAdd(2, 3)); // 输出日志并计算结果
高阶函数让我们可以灵活地扩展已有函数的功能,比如添加缓存逻辑。
结合这三个概念,我们可以实现一个通用的函数缓存工具,如下:
function memoize(func) {const cache = new Map(); // 使用 Map 存储缓存return function (...args) {const key = JSON.stringify(args); // 将参数序列化为字符串作为键if (cache.has(key)) {console.log('从缓存中获取结果:', key);return cache.get(key);}const result = func(...args); // 调用原始函数计算结果cache.set(key, result); // 缓存结果return result;};
}
用法如下:
const add = (a, b) => a + b;
const cachedAdd = memoize(add);console.log(cachedAdd(10, 20)); // 输出 30
console.log(cachedAdd(10, 20)); // 从缓存中获取结果,输出 30
在这个实现中,我们用Map来存储缓存,key是序列化的参数值,这样可以保证每一组参数对应一个缓存结果。调用时,先检查缓存中是否存在结果,存在就直接返回,不存在就计算后存入缓存。
虽然函数缓存非常高效,但它并不适合所有场景。以下是一些常见的适用场景:
1、昂贵的函数调用:比如涉及复杂计算的场景。假设我们需要计算一个斐波那契数列的值:
function fibonacci(n) {if (n <= 1) return n;return fibonacci(n - 1) + fibonacci(n - 2);
}
用递归计算时,很多输入会重复计算。通过缓存优化:
const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(40)); // 快速计算大值
2、输入有限且重复率高:比如函数的输入是有限的枚举值,且经常重复。例如,一个根据用户 ID 获取用户信息的函数:
const getUserInfo = memoize((userId) => {console.log(`Fetching data for user ${userId}`);return { id: userId, name: `User${userId}` }; // 模拟 API 请求
});console.log(getUserInfo(1));
console.log(getUserInfo(1)); // 第二次调用直接从缓存获取
3、纯函数的计算:纯函数是指对于相同输入,总是返回相同输出的函数,比如数学函数:
const square = memoize((x) => x * x);
console.log(square(5)); // 输出 25
console.log(square(5)); // 从缓存中获取,输出 25
4、递归函数的优化:尤其是那些可以利用之前结果的递归场景,比如阶乘计算。
“函数缓存的实现可以依赖闭包、高阶函数和柯里化。核心思路是使用一个存储计算结果的缓存对象(比如Map),每次调用时先检查缓存中是否有结果,如果有则直接返回,否则进行计算并存储结果。”
然后给出简洁的实现代码:
function memoize(func) {const cache = new Map();return function (...args) {const key = JSON.stringify(args);if (cache.has(key)) return cache.get(key);const result = func(...args);cache.set(key, result);return result;};
}
最后补充应用场景,比如复杂计算(如递归函数)和高频调用的纯函数等,显示你的全面思考能力。这样,不仅技术点到位,还能展现你的工程实践思路。
相关文章:
Javascript中如何实现函数缓存?函数缓存有哪些应用场景?
今天要聊的一个很经典的问题——如何在JavaScript中实现函数缓存,以及它有哪些应用场景。 我们先来明确一下,函数缓存是什么。简单来说,函数缓存是将函数的运算结果存储起来,以便下次用到相同的输入时,可以直接返回结…...
子页面访问父页面
子页面访问父页面的方式主要依赖于页面之间的关系,特别是它们是否处于同一域、是否是嵌套在 <iframe> 中、或者通过弹出窗口打开。下面是几种常见的子页面访问父页面的方法: 1. 通过 window.parent 访问父页面(适用于嵌套的 iframe&am…...
芯片级IO (Pad) Ring IP Checklist
SoC top顶层数字后端实现都会涉及到IO Ring (PAD Ring)的设计。这里面包括VDD IO,VDDIO IO, Signal IO, Corner IO,Filler IO,IO power cut cell等等。 数字后端零基础入门系列 | Innovus零基础LAB学习Day2 数字IC后端实现TOP F…...
计算机毕业设计论文指导
计算机毕业设计论文指导 计算机毕业设计辅导一站式!太香了💪 [赞R][赞R][赞R]嗨喽!计算机专业的宝子们! 计算机毕设辅导专业靠谱的他来了!! 是不是还在为选题程序不会做而感到苦难? 论文没思路赶…...
Electron-Vue 开发下 dev/prod/webpack server各种路径设置汇总
背景 在实际开发中,我发现团队对于这几个路径的设置上是纯靠猜的,通过一点点地尝试来找到可行的路径,这是不应该的,我们应该很清晰地了解这几个概念,以下通过截图和代码进行细节讲解。 npm run dev 下的路径如何处理&…...
Vue.js前端框架教程9:Vue插槽slot用法
文章目录 插槽(Slots)无名插槽(默认插槽)具名插槽reference 插槽使用 v-slot 的缩写语法 插槽(Slots) 在 Vue 中,插槽(Slots)是一种组件内容分发的机制,允许…...
初学stm32 --- NVIC中断
目录 STM32 NVIC 中断优先级管理 NVIC_Type: ISER[8]: ICER[8]: ISPR[8]: ICPR[8]: IABR[8]: IP[240]: STM32 的中断分组: 中断优先级分组函数 NVIC_PriorityGroupConfig 中断初始化函…...
Jest 入门指南:从零开始编写 JavaScript 单元测试
前言 在前端开发中,单元测试已经成为确保代码质量和稳定性的关键步骤。Jest 作为由 Facebook 开发和维护的功能强大的 JavaScript 测试框架,以其易于配置、丰富的功能和开箱即用的特性,成为众多开发者的首选工具。本文旨在引导你从零开始&am…...
【Java Web】Axios实现前后端数据异步交互
目录 一、Promise概述 二、Promise基本用法 三、async和await关键字 四、Axios介绍 4.1 Axios基本用法 4.2 Axios简化用法之get和post方法 五、Axios拦截器 六、跨域问题处理 一、Promise概述 axios是代替原生的ajax实现前后端数据交互的一套新解决方案,而…...
React 第十七节 useMemo用法详解
概述 useMemo 是React 中的一个HOOK,用于根据依赖在每次渲染时候缓存计算结果; 大白话就是,只有依赖项发生变化时候,才会重新渲染为新计算的值,否则就还是取原来的值,有点类似 vue 中的 computed 计算属性…...
鸿蒙项目云捐助第十五讲云数据库的初步使用
鸿蒙项目云捐助第十五讲云数据库的初步使用 在华为云技术使用中,前面使用了云函数,接下来看一下华为云技术中的另外一个技术云数据库的使用。 一、云数据库的创建 这里使用华为云数据库也需要登录到AppGallery Connect平台中,点击进入到之…...
如何构建一个可信的联邦RAG系统。
今天给大家分享一篇论文。 题目是:C-RAG:如何构建一个可信的联邦检索RAG系统。 论文链接:https://arxiv.org/abs/2412.13163 论文概述 尽管大型语言模型 (LLM) 在各种应用中展现出令人印象深刻的能力,但它们仍然存在可信度问题ÿ…...
【深度学习之三】FPN与PAN网络详解
FPN与PAN:深度学习中的特征金字塔网络与路径聚合网络 在深度学习的领域里,特征金字塔网络(Feature Pyramid Networks,简称FPN) 和 路径聚合网络(Path Aggregation Network,简称PAN)…...
Qt学习笔记第71到80讲
第71讲 事件过滤器的方式实现滚轮按键放大 事件体系(事件派发 -> 事件过滤->事件分发->事件处理)中,程序员主要操作的是事件分发与事件处理。我们之前已经通过继承QTextEdit来重写事件实现Ctrl加滚轮的检测,还有一种处理…...
以管理员身份运行
同时按下Ctrl Shift Esc键打开任务管理器,在任务管理器的左上角,点击“文件”菜单,在下拉菜单中选择“新建任务” 在弹出的对话框中,输入您想要运行的程序的名称。如果您不确定程序的确切名称,可以点击“浏览”来找到…...
用 Python 实现井字棋游戏
一、引言 井字棋(Tic-Tac-Toe)是一款经典的两人棋类游戏。在这个游戏中,玩家轮流在 3x3 的棋盘上放置自己的标记,通常是 “X” 和 “O”,第一个在棋盘上连成一线(横、竖或斜)的玩家即为获胜者。…...
06 实现自定义AXI DMA驱动
为什么要实现自定义AXI DMA驱动 ZYNQ 的 AXI DMA 在 Direct Register DMA (即 Simple DMA)模式下可以通过 AXIS 总线的 tlast 提前结束传输,同时还可以在 BUFFLEN 寄存器中读取到实际传输的字节数,但是通过 Linux 的 DMA 驱动框架…...
SpringBoot集成ENC对配置文件进行加密
在线MD5生成工具 配置文件加密,集成ENC 引入POM依赖 <!-- ENC配置文件加密 --><dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2.1.2</ver…...
初学stm32 ——— 串口通信
目录 STM32的串口通信接口 UART异步通信方式特点: 串口通信过程 STM32串口异步通信需要定义的参数: USART框图: 常用的串口相关寄存器 串口操作相关库函数 编辑 串口配置的一般步骤 STM32的串口通信接口 UART:通用异步收发器USART&am…...
qwt 多Y轴 项目效果
项目场景: 在做一个半导体上位机软件项目实践中,需要做一个曲线展示和分析界面,上位机主题是用qt框架来开发,考虑到目前qt框架的两种图标库,一个是qcustomplot 一个是 qwt。之所以采用qwt ,根本原因是因为…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
