
纯函数是函数式编程的基石,其核心判据仅有两条:输入相同时输出恒定,且执行过程不产生任何副作用;返回值本身不属于副作用,字符串字面量等常量亦不破坏纯性。
纯函数是函数式编程的基石,其核心判据仅有两条:输入相同时输出恒定,且执行过程不产生任何副作用;返回值本身不属于副作用,字符串字面量等常量亦不破坏纯性。
在函数式编程实践中,能否准确识别纯函数,直接关系到代码的可测试性、可维护性与并发安全性。一个常见误区是将“返回值”或“使用常量”误判为副作用——这恰恰混淆了函数行为本质与实现细节。
✅ 什么是真正的纯函数?
纯函数必须同时满足两个不可妥协的条件:
- 确定性(Determinism):对任意相同输入,始终返回完全相同的输出;
-
无副作用(No Side Effects):执行过程中不修改任何外部状态,包括但不限于:
- 不修改全局变量、模块级变量或闭包外的可变引用;
- 不进行 I/O 操作(如
console.log、fetch、fs.writeFile); - 不改变传入的对象/数组(即不执行
obj.x = 1或arr.push()); - 不依赖易变的外部状态(如
Date.now()、Math.random()、DOM 节点属性)。
⚠️ 关键澄清:返回值不是副作用,字符串字面量(如
"Hi, I'm ")不是外部状态。它们是函数体内的静态常量,不影响 referential transparency(所指透明性)。例如:
function greet(name) {
return "Hi, I'm " + name; // ✅ 纯函数
}
greet("Alice") // 始终返回 "Hi, I'm Alice"
greet("Bob") // 始终返回 "Hi, I'm Bob"
该函数完全符合纯函数定义:输入决定输出,无任何外部交互或状态变更。
❌ 常见的非纯函数模式(附对比示例)
| 类型 | 示例 | 问题分析 |
|---|---|---|
| 依赖可变外部变量 |
let prefix = "Hello";<br>function impureGreet(name) {<br> return prefix + ", " + name;<br>} | prefix 可被外部修改 → 同一输入可能产生不同输出 |
|
| 执行 I/O 操作 |
function logGreet(name) {<br> console.log("Greeting:", name);<br> return `Hi, ${name}`;<br>} | console.log 是典型副作用 → 破坏纯性 |
|
| 修改入参对象 | function mutateUser(user) {<br> user.lastSeen = Date.now();<br> return user;<br>} |
直接篡改传入对象 → 外部引用状态被污染 |
| 依赖时间/随机数 | function getTimeStamp() {<br> return Date.now();<br>} |
输出随调用时刻变化 → 违反确定性 |
✅ 进阶实践建议
-
优先封装常量:若需复用
"Hi, I'm ",可将其作为默认参数或模块常量,而非强行拆解为独立参数——这不会提升纯度,反而增加调用负担:// 更自然、更简洁的纯写法 const greet = (name, prefix = "Hi, I'm ") => `${prefix} ${name}`; -
善用闭包构建纯上下文:闭包捕获的不可变值(如
const greeting = "Hello")是安全的,因其生命周期内恒定:const createGreeter = (prefix) => (name) => `${prefix}, ${name}`; const hiGreeter = createGreeter("Hi"); // ✅ 返回纯函数 hiGreeter("Tom") // "Hi, Tom" —— 稳定、可预测、无副作用 工具辅助验证:在 TypeScript 或借助 ESLint 插件(如
eslint-plugin-functional)中启用纯函数检查规则,可提前拦截隐式副作用。
总结
判断纯函数,应回归本质:只问两件事——它是否总给出相同答案?它是否悄悄改变了什么?
只要答案都是“否”,哪怕内部用了字符串拼接、数学运算或深层嵌套表达式,它依然是纯粹的。摒弃对“常量”“返回值”“语法糖”的误解,才能真正掌握函数式编程的确定性力量——这也是现代前端框架(如 React 的 Pure Components)、状态管理(如 Redux 的 reducer)及并发编程(如 Erlang/Elixir)赖以稳固的底层契约。










