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 仍被共享,后续修改穿透到新对象实操建议:
[...arr] 或 Array.from(arr) 替代 arr.slice()(更语义清晰)structuredClone(obj)(现代环境),或 JSON.parse(JSON.stringify(obj))(仅限可序列化场景)Object.freeze() 在开发期捕获意外赋值(注意:它不递归冻结嵌套对象)纯函数 = 相同输入 → 永远相同输出 + 零副作用。它不能读取或修改任何外部变量(包括 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 —— 它们都是副作用,应由调用方统一处理Promise 是可以的,但函数本身不能 await 或 then,否则控制流不可预测getProp(obj, path) 必须确保不修改 obj,也不依赖 this 或闭包变量完全禁用 = 和 .push() 不现实。关键是在**状态变更的关键路径上强制不可变**,比如 React 的 setState、Redux 的 reducer、Zustand 的 set 回调。
性能与兼容性提醒:
[...oldArr, newItem] 比 oldArr.concat(newItem) 更快,且可读性一致immer 是合理选择——它让你写“看起来可变”的代码,底层自动产出不可变副本;但别把它当成逃避理解不可变的借口Array.prototype.concat() 或 slice(0),但要清楚它们仍是浅拷贝容易被忽略的一点:函数式不是为了炫技。当你发现为保持不可变而写了 5 层嵌套 map + reduce,反而让逻辑难懂、调试困难,这时候该退一步——用清晰的 for 循环 + 注释说明“此处需不可变”,比强行函数式更符合工程实际。