当前位置:首页 > 技术 > 正文内容

JavaScript函数式编程中的备忘录模式与纯函数边界

访客 技术 2026年8月14日 3

在函数式编程中,纯函数因其可预测性而备受推崇——相同的输入永远产生相同的输出。这一特性天然支持了"备忘录"(Memoization)技术,即缓存函数计算结果,避免重复执行昂贵运算。应用此技术时,我们只在首次计算表达式时执行,并将结果存入缓存,后续调用直接返回缓存值。

斐波那契数列是演示备忘录的经典案例,它的递归定义天然具有大量重复计算。原始实现如下:

const fibonacci = (n) => {
  if (n === 0) return 0
  if (n === 1) return 1
  return fibonacci(n - 2) + fibonacci(n - 1)
}
console.log(fibonacci(10)) // 55

随着 n 增大,计算时间呈指数增长。引入备忘录后,我们用一个缓冲区存储已知结果:

const memoFib = (() => {
  const cache = {}
  const fib = (n) => {
    if (n in cache) return cache[n]
    if (n === 0) cache[n] = 0
    else if (n === 1) cache[n] = 1
    else cache[n] = fib(n - 2) + fib(n - 1)
    return cache[n]
  }
  return fib
})()
console.log(memoFib(10)) // 55,速度大幅提升

注意,这里使用 IIFE 和闭包隐藏了缓存,避免全局变量污染。但需谨慎:若缓存数据量过大,可能导致内存耗尽。因此,备忘录并非适用于所有纯函数——仅当函数调用频繁、计算开销大且输入范围有限时值得采用。

纯函数还有一个显著优势:所有依赖通过参数显式传递,没有隐藏的外部状态。阅读源码时,你掌控了函数行为的全部信息;使用函数时,无需担心意外副作用。但过度追求"无副作用"也可能不切实际。实际开发中,我们应努力减少副作用,当不可避免时,需思考如何限制其影响范围。

处理全局状态(读取或修改)的经典做法是:将全局状态作为参数传给函数;若函数需要更新状态,不直接修改,而是返回新状态;由调用方负责用新状态更新全局变量。这正是 Redux reducer 的核心模式:(previousState, action) => newState。reducer 必须保持 previousState 不变。

当一个函数因调用非纯函数而变得不纯时,可通过依赖注入来解耦。例如,一个生成随机文件名的函数内部使用了随机字母生成器:

const getRandomFileName = (extension = '') => {
  const NAME_LENGTH = 12
  const parts = []
  for (let i = 0; i < NAME_LENGTH; i++) {
    parts.push(getRandomLetter())
  }
  return parts.join('') + extension
}

改进版本将随机字母生成函数作为参数注入:

const getRandomFileName2 = (extension = '', randomLetterFn) => {
  const NAME_LENGTH = 12
  const parts = []
  for (let i = 0; i < NAME_LENGTH; i++) {
    parts.push(randomLetterFn())
  }
  return parts.join('') + extension
}

相应的随机字母生成函数也可注入随机数生成器:

const getRandomLetter = (random = Math.random) => {
  const min = 'A'.charCodeAt()
  const max = 'Z'.charCodeAt()
  return String.fromCharCode(Math.floor(random() * (1 + max - min)) + min)
}

即使我们将函数写得尽可能纯,JavaScript 的动态特性仍可能让纯函数"变质"。考虑一个看似纯函数的求和函数:

const sum3 = (x, y, z) => x + y + z

初看它只依赖参数,不修改变量,不执行 I/O。但若传入一个自定义对象,其 valueOf 方法返回随机数:

const obj = { valueOf: Math.random }
console.log(sum3(obj, 1, 2)) // 每次输出不同

因此,JavaScript 中函数的纯度不仅取决于函数体本身,还取决于运行时参数的行为。这提醒我们,在边界情况下,纯函数的假设可能被打破,需要格外审慎。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

linux screen 用法详情 (nohup 的替代方案)

一、screen 是什么?能干嘛?screen 是一个终端复用器,可以:在一个 SSH 会话中开多个“虚拟终端”SSH 断线后,程序仍然在后台运行随时重新连接到原来的会话特别适合:nohup 的替代方案跑脚本 / 爬虫 / 训练模型运维、远程开发二、安装 screen# CentOS / Rocky / Almayum install -y screen# Debian / Ubuntuapt i...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。