JavaScript函数式编程核心是约束副作用和保证可预测性,即数据不可变与函数纯化;否则map、reduce等仅为语法糖。

JavaScript 函数式编程不是“用函数写代码”就完事,核心在于**约束副作用**和**保证可预测性**:数据不可变(immutable),函数必须是纯的(pure function)。做不到这两点,其余诸如 map、reduce、柯里化,都只是语法糖,不是函数式。
为什么直接修改对象/数组会破坏函数式契约
JavaScript 默认所有对象、数组都是可变的。一旦函数内部执行了 arr.push(x)、obj.name = 'new' 或 arr.sort(),它就不再是纯函数——调用结果依赖外部状态,且多次调用可能产生不同副作用(比如原数组被改乱)。
常见错误现象:
- 使用
filter后又在回调里修改了原对象字段,导致后续逻辑读到脏数据 - 把
useState的 state 直接 push 进去,触发 React 警告甚至渲染异常 - 用
Object.assign({}, obj, {x: 1})浅拷贝,但obj.nested仍被共享,后续修改穿透到新对象
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 默认用
[...arr]或Array.from(arr)替代arr.slice()(更语义清晰) - 深层嵌套对象改用
structuredClone(obj)(现代环境),或JSON.parse(JSON.stringify(obj))(仅限可序列化场景) - 用
Object.freeze()在开发期捕获意外赋值(注意:它不递归冻结嵌套对象)
如何写出真正纯的 JavaScript 函数
纯函数 = 相同输入 → 永远相同输出 + 零副作用。它不能读取或修改任何外部变量(包括 Math.random()、Date.now()、localStorage、全局变量、参数对象属性)。
典型反例:
function formatPrice(price) {
return `$${price.toFixed(2)} (${new Date().getFullYear()})`; // ❌ 依赖 Date.now()
}
正确写法(将“当前年份”作为参数传入):
function formatPrice(price, year) {
return `$${price.toFixed(2)} (${year})`; // ✅ 纯函数
}
其他要点:
- 避免在函数体内调用
console.log、alert、fetch—— 它们都是副作用,应由调用方统一处理 - 如果必须封装异步逻辑(如 API 请求),返回
Promise是可以的,但函数本身不能await或then,否则控制流不可预测 -
工具函数如
getProp(obj, path)必须确保不修改obj,也不依赖this或闭包变量
不可变更新在实际项目中的落地成本与取舍
完全禁用 = 和 .push() 不现实。关键是在**状态变更的关键路径上强制不可变**,比如 React 的 setState、Redux 的 reducer、Zustand 的 set 回调。
性能与兼容性提醒:
-
[...oldArr, newItem]比oldArr.concat(newItem)更快,且可读性一致 - 大量频繁更新深层嵌套结构时,
immer是合理选择——它让你写“看起来可变”的代码,底层自动产出不可变副本;但别把它当成逃避理解不可变的借口 - IE11 不支持扩展运算符?那就用
Array.prototype.concat()或slice(0),但要清楚它们仍是浅拷贝
容易被忽略的一点:函数式不是为了炫技。当你发现为保持不可变而写了 5 层嵌套 map + reduce,反而让逻辑难懂、调试困难,这时候该退一步——用清晰的 for 循环 + 注释说明“此处需不可变”,比强行函数式更符合工程实际。











