函数式编程强调数据不可变和函数无副作用,在JavaScript中实现函数式数据结构,核心是用纯函数处理数据,每次操作都返回新的数据结构而不修改原值。这种方式能让状态变化可追踪、便于测试和并发处理。

什么是纯函数与不可变数据
纯函数是指给定相同输入永远返回相同输出,且执行过程中不产生副作用(如修改外部变量、改变参数本身)的函数。不可变数据则是创建后不能被更改的数据,任何更新操作都生成新副本。在JS里,基本类型天然不可变,但对象和数组是引用类型,直接赋值或改属性会联动原数据。
下面这段代码展示了非纯函数写法,它修改了传入的数组,违反了函数式原则:
function addItemBad(list, item) {
list.push(item); // 直接修改原数组,产生副作用
return list;
}
const arr = [1, 2];
addItemBad(arr, 3);
console.log(arr); // [1, 2, 3],原数组已被改变
纯函数版本应当返回新数组,原数组保持不动:
function addItemGood(list, item) {
return [...list, item]; // 展开运算符创建新数组
}
const arr2 = [1, 2];
const newArr = addItemGood(arr2, 3);
console.log(arr2); // [1, 2],原数组未变
console.log(newArr); // [1, 2, 3]
用纯函数实现基础列表结构
除了数组,我们也可以用对象模拟链表来实现纯函数列表。链表每个节点包含值和指向下一节点的引用,插入时只创建新节点并指向旧头部,旧链表完全保留。这种结构在频繁头部插入时性能优于数组拷贝。
以下代码定义了一个简单的纯函数链表及头部插入函数:
// 链表节点:{ value, next }
function cons(value, list) {
return { value, next: list }; // 返回新节点,不修改原list
}
function toArray(list) {
const result = [];
let cur = list;
while (cur) {
result.push(cur.value);
cur = cur.next;
}
return result;
}
const empty = null;
const listA = cons(1, cons(2, empty)); // 链表:2 -> 1
const listB = cons(3, listA); // 新链表:3 -> 2 -> 1,listA不变
console.log(toArray(listA)); // [2, 1]
console.log(toArray(listB)); // [3, 2, 1]
这种链表实现中所有操作都是纯函数,旧数据可被其他逻辑安全引用。缺点是随机访问慢,需要遍历。实际项目中可根据场景在数组展开与链表之间取舍。
嵌套数据的纯函数更新
业务中常见深层对象,如用户配置包含嵌套偏好。用展开运算符只做浅拷贝,修改深层属性会污染原对象。此时应使用递归更新或编写小工具函数来保证纯函数特性。
下面示范一个深层设置的纯函数工具:
function setIn(obj, keys, value) {
if (keys.length === 0) return value;
const [first, ...rest] = keys;
const child = obj && obj[first] ? obj[first] : {};
return {
...obj,
[first]: setIn(child, rest, value)
};
}
const state = { user: { profile: { theme: 'light' } } };
const newState = setIn(state, ['user', 'profile', 'theme'], 'dark');
console.log(state.user.profile.theme); // 'light',原状态未变
console.log(newState.user.profile.theme); // 'dark'
该函数通过递归对每一层做对象展开,只重建被修改路径上的节点,其他分支仍共享引用,兼顾纯函数与内存效率。若项目复杂,也可引入Immutable.js等库,但理解原理有助于写出轻量纯函数代码。
纯函数数据结构的性能与权衡
纯函数每次生成新数据会带来一定内存和拷贝开销,但JS引擎对短数组展开优化很好,中小数据量无感知。对于大型不可变树,可采用结构共享(如上面递归只建新路径)减少浪费。
下表对比常见操作在可变与纯函数下的差异:
| 操作 | 可变写法 | 纯函数写法 | 特点 |
|---|---|---|---|
| 头部插入 | array.unshift(x) | [x, ...array] | 纯函数更安全,大数组稍慢 |
| 修改属性 | obj.k = v | {...obj, k: v} | 纯函数保留旧引用 |
| 深层改值 | obj.a.b.c = v | 递归setIn | 纯函数需工具支持 |
总体看,纯函数数据结构以轻微性能换来了可预测性和调试便利,在前端状态管理(如Redux)中已成为主流实践。掌握基础实现技巧,就能在不依赖重库时写出稳健的函数式代码。
JavaScript函数式编程纯函数修改时间:2026-08-05 00:33:27