函数式编程本质是用纯函数建模、不可变数据传递和高阶函数组合来提升代码可预测性与可靠性;纯函数要求相同输入恒得相同输出且无副作用,不可变性强调不修改原内存而创建新数据,高阶函数通过声明式抽象实现逻辑复用与组合。

JavaScript 函数式编程不是“用函数写代码”就叫函数式,而是用纯函数建模、靠不可变数据传递、借高阶函数组合逻辑——它本质是换一种方式思考“如何让代码更可预测、更少出错”。
纯函数:为什么 add(2, 3) 必须永远返回 5
纯函数是函数式编程的基石,不是风格偏好,而是行为契约:
- 相同输入 → 永远相同输出(比如
const add = (a, b) => a + b) - 不读取或修改外部变量(不能依赖
new Date()、Math.random()、全局userConfig) - 不修改传入参数(禁止在函数里
arr.push(x)或obj.name = 'new')
常见错误现象:Array.prototype.sort() 是非纯函数——它原地排序,改变原数组;换成 arr.slice().sort() 才算靠近纯函数。
为什么必须这样?因为只有纯函数才能放心缓存(memoize)、并行执行、单元测试时无需 mock 时间或网络。
立即学习“Java免费学习笔记(深入)”;
不可变性:不是“不改数据”,而是“不改同一块内存”
JavaScript 没有原生不可变类型,所以“不可变”是约定+实践:
- 数组操作用
map、filter、concat、展开运算符[...arr, newItem],不用push、splice - 对象更新用
{ ...obj, name: 'Alice' }或Object.assign({}, obj, { name: 'Alice' }),不用obj.name = 'Alice' - 深层嵌套更新推荐
immer库,或手写递归拷贝(但注意性能)
容易踩的坑:以为 const arr = [1,2,3]; const newArr = arr.map(x => x * 2); 就够了——其实如果 arr 里含对象,map 后的新数组仍引用原对象,改 newArr[0].id 还是会污染原数据。
高阶函数与组合:把逻辑“拼积木”,而不是“写流程”
函数式编程里,map、filter、reduce 不只是数组方法,它们是声明式抽象接口:
const isAdult = user => user.age >= 18; const toNameUpper = user => user.name.toUpperCase(); const namesOfAdults = users => users.filter(isAdult).map(toNameUpper);
这种写法比 for 循环更易读、更易拆解、更易复用。进一步可以封装组合:
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
const shout = compose(str => str + '!', str => str.toUpperCase());
shout('hello'); // 'HELLO!'
注意点:compose 从右向左执行,而 pipe(从左向右)更符合直觉,选哪个取决于团队习惯;另外,过度嵌套组合会让调试变难——每个中间函数最好有明确命名和独立测试。
函数式编程最难的部分,不是记概念,而是判断“哪里该纯、哪里可妥协”。真实项目中,I/O、状态管理、UI 渲染天然带副作用,硬套纯函数反而增加复杂度。关键是守住核心数据流(比如 API 响应处理、表单校验、列表转换),让副作用收口、可见、可控。











