JavaScript 中如何实现函数缓存有哪些应用场景面试中怎么回答精彩这是一个很适合拉开差距的问题。因为它不只是“会不会写个缓存对象”还涉及闭包高阶函数Map / WeakMap记忆化memoization性能优化工程场景设计如果你回答得好面试官会觉得你对 JavaScript 的函数式思想和性能优化都有理解。一、先说什么是函数缓存函数缓存本质上就是把函数输入和输出建立映射关系当下次传入相同参数时直接返回之前的结果而不是重新计算。它在函数式编程里通常叫Memoization记忆化一句话理解用空间换时间。比如一个计算很耗时function add(a, b) { console.log(执行了计算); return a b; }如果我们每次都传1, 2其实结果永远是3完全没必要重复算。二、为什么要做函数缓存主要目的是减少重复计算提升性能降低 CPU 消耗避免重复请求或重复处理提高响应速度尤其适合纯函数计算成本高相同输入频繁出现结果可复用三、最简单的函数缓存怎么实现最基础思路用一个对象或Map来存储参数和结果的映射。1. 基础版实现function memoize(fn) { const cache {}; return function (...args) { const key JSON.stringify(args); if (key in cache) { console.log(走缓存); return cache[key]; } console.log(重新计算); const result fn.apply(this, args); cache[key] result; return result; }; }使用function sum(a, b) { return a b; } const memoizedSum memoize(sum); console.log(memoizedSum(1, 2)); // 重新计算 - 3 console.log(memoizedSum(1, 2)); // 走缓存 - 32. 这个实现的核心点闭包cache被保存在返回函数的作用域里不会被销毁。高阶函数memoize接收一个函数返回一个增强后的函数。参数序列化通过JSON.stringify(args)作为缓存 key。四、更推荐用 Map 实现因为普通对象会有一些问题key 只能是字符串可能和原型链属性冲突可读性和语义不如Map所以更推荐function memoize(fn) { const cache new Map(); return function (...args) { const key JSON.stringify(args); if (cache.has(key)) { console.log(命中缓存); return cache.get(key); } const result fn.apply(this, args); cache.set(key, result); return result; }; }五、如果参数是对象怎么办如果参数里有对象JSON.stringify虽然有时能用但不总是可靠属性顺序可能影响 key循环引用会报错函数、undefined、symbol处理不稳定这时候有两种思路。1. 自定义 key 生成策略把“如何生成缓存 key”交给调用方。function memoize(fn, resolver) { const cache new Map(); return function (...args) { const key resolver ? resolver(...args) : JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result fn.apply(this, args); cache.set(key, result); return result; }; }使用const getUser memoize( (user) user.name, (user) user.id );这样就不依赖对象序列化了。2. 多层 Map / WeakMap这是更高级的做法适合对象参数场景。比如基本类型参数用Map对象参数用WeakMap示意写法function memoize(fn) { const root new Map(); return function (...args) { let current root; for (let i 0; i args.length; i) { const arg args[i]; const isObject arg ! null (typeof arg object || typeof arg function); const isLast i args.length - 1; if (!current.has(arg)) { current.set(arg, isLast ? { value: undefined, computed: false } : new Map()); } current current.get(arg); } if (current.computed) { return current.value; } const result fn.apply(this, args); current.value result; current.computed true; return result; }; }不过这个版本对对象键与基本类型键混用时还可以继续优化。面试里说到这个思路就已经很加分了。六、经典应用斐波那契数列优化这是最常见的面试示例。普通递归function fib(n) { if (n 1) return n; return fib(n - 1) fib(n - 2); }这个会有大量重复计算。加缓存后function memoize(fn) { const cache new Map(); return function (...args) { const key JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result fn.apply(this, args); cache.set(key, result); return result; }; } const fib memoize(function (n) { if (n 1) return n; return fib(n - 1) fib(n - 2); }); console.log(fib(40));这样性能会提升很多。七、函数缓存的常见应用场景这是面试里很重要的一部分。不要只讲实现一定要讲场景。1. 复杂计算结果缓存适合那些计算逻辑复杂输入重复率高结果稳定不变例如斐波那契大数据运算数学公式计算表格复杂筛选const memoizedCalc memoize(expensiveFn);2. 接口请求缓存对于相同参数的请求可以直接复用结果避免重复发请求。例如const memoizedFetchUser memoize(async (id) { const res await fetch(/api/user/${id}); return res.json(); });典型用途用户详情查询配置数据读取字典表查询地区数据查询不过实际中会更常做成Promise 缓存请求去重3. 前端搜索/筛选/排序结果缓存比如一个大列表相同关键词相同筛选条件相同排序规则可以直接返回上一次结果减少重复处理。4. React / Vue 中的计算结果复用比如组件里有昂贵计算列表过滤树结构转换图表数据处理虽然框架层有自己的机制React 的useMemoVue 的computed但本质上也是函数缓存思想。你可以说useMemo和computed某种程度上就是基于依赖变化的结果缓存。这句话很加分。5. 模板编译 / AST 转换 / 解析类任务像Markdown 转 HTMLJSON schema 转表单结构AST 编译正则解析如果同样输入会多次出现非常适合缓存。6. 防止重复异步操作例如同一个按钮重复点击触发同一请求同一资源重复加载同一模块重复初始化这个时候缓存Promise很有用。function memoizeAsync(fn) { const cache new Map(); return async function (...args) { const key JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const promise fn.apply(this, args); cache.set(key, promise); try { return await promise; } catch (err) { cache.delete(key); throw err; } }; }这个版本很实战。八、函数缓存的注意点和缺点面试中如果你能主动讲风险回答会更成熟。1. 只适合“相同输入必然得到相同输出”的场景也就是更适合纯函数。不适合这种函数function getNow() { return Date.now(); }因为每次都应该返回新值缓存就错了。也不适合依赖外部状态的函数let count 0; function add() { return count; }2. 缓存会占用内存缓存不是免费的。如果输入种类很多缓存会越来越大可能造成内存膨胀命中率低得不偿失所以有时候要加最大容量限制LRU 淘汰策略TTL 过期时间3. 参数序列化可能不可靠用JSON.stringify(args)有局限对象属性顺序问题循环引用特殊值处理问题函数无法稳定序列化所以工业场景一般不会直接无脑 stringify。4. 异步函数缓存要考虑失败回滚如果缓存的是 Promise失败后是否保留通常做法是成功保留失败删除否则会一直缓存一个 reject 的 Promise。九、手写一个更实用的 memoize这个版本面试里写出来很稳。function memoize(fn, resolver) { const cache new Map(); return function (...args) { const key resolver ? resolver.apply(this, args) : JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result fn.apply(this, args); cache.set(key, result); return result; }; }使用function multiply(a, b) { console.log(执行计算); return a * b; } const memoMultiply memoize(multiply); console.log(memoMultiply(2, 3)); // 执行计算 6 console.log(memoMultiply(2, 3)); // 直接缓存 6如果你想再加分可以再说如果是工程场景我还会继续增强比如加入过期时间、容量限制、删除缓存、手动清空缓存等能力。十、面试时怎么回答更精彩建议按这个结构回答1. 先下定义函数缓存本质是把函数的输入和输出做映射下次相同输入时直接复用结果避免重复计算本质上是用空间换时间。2. 说实现思路在 JavaScript 中通常通过闭包保存一个缓存容器比如对象或Map。再通过高阶函数包装原函数调用时根据参数生成 key如果命中缓存就直接返回否则执行原函数并存储结果。3. 说应用场景它常见于复杂计算优化、接口请求缓存、前端筛选排序、组件计算属性优化以及异步请求去重等场景。4. 说限制和工程点但它更适合纯函数不适合依赖时间、随机数或外部状态的函数。同时还要注意缓存增长、内存占用、key 设计、异步失败处理等问题。十一、2 分钟背诵版面试答案下面这版你可以直接背。2 分钟版JavaScript 中的函数缓存本质上就是把函数的参数和计算结果建立映射关系。当再次传入相同参数时直接返回缓存结果而不是重复执行函数这种思想也叫 memoization核心是用空间换时间。实现上一般会通过闭包保存一个缓存容器比如Map然后写一个高阶函数去包装原函数。调用时先根据参数生成一个 key如果缓存里已经有结果就直接返回没有的话再执行原函数并把结果存起来。比如简单场景可以用JSON.stringify(args)作为 key更复杂的场景可以支持自定义resolver如果参数里有对象也可以结合WeakMap做更可靠的缓存它的典型应用场景主要有几个第一复杂计算缓存比如斐波那契、表格大数据筛选、树结构转换第二请求缓存或请求去重比如相同参数的接口请求直接复用结果第三前端框架中的计算结果复用比如 React 的useMemo、Vue 的computed本质上也有函数缓存的思想。但是函数缓存也不是所有场景都适合它更适用于纯函数也就是相同输入一定得到相同输出。如果函数依赖时间、随机数、外部状态就不适合缓存。此外还要考虑缓存过大带来的内存问题以及异步缓存时 Promise 失败后是否需要清除缓存。如果是面试手写我一般会用Map 闭包 高阶函数实现一个基础版如果是工程场景我会进一步补充过期时间、容量限制或者手动清空缓存的能力。十二、30 秒精简版函数缓存就是把函数参数和结果做映射相同输入直接返回历史结果避免重复计算本质是用空间换时间。JavaScript 里一般通过闭包保存一个Map再用高阶函数包装目标函数调用时先查缓存命中就返回没命中才执行并存储。它常用于复杂计算优化、接口请求缓存、请求去重以及 ReactuseMemo、Vuecomputed这类结果复用场景。但它更适合纯函数还要注意 key 设计、内存占用和异步失败处理。十三、附一个可直接写出来的代码版本function memoize(fn, resolver) { const cache new Map(); return function (...args) { const key resolver ? resolver.apply(this, args) : JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result fn.apply(this, args); cache.set(key, result); return result; }; } // 使用示例 function heavySum(a, b) { console.log(执行计算); return a b; } const memoizedSum memoize(heavySum); console.log(memoizedSum(1, 2)); // 执行计算 console.log(memoizedSum(1, 2)); // 走缓存