不可变数据(Immutability)
不可变数据是函数式编程的核心概念,数据一旦创建就不能被修改。
📚 基本概念
可变 vs 不可变
// 可变(Mutable)
const arr = [1, 2, 3];
arr.push(4); // 修改原数组
console.log(arr); // [1, 2, 3, 4]
// 不可变(Immutable)
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // 创建新数组
console.log(arr); // [1, 2, 3](原数组未变)
console.log(newArr); // [1, 2, 3, 4]🎯 实现方式
1. Object.freeze()
浅冻结对象:
const obj = { name: 'Alice', age: 30 };
Object.freeze(obj);
obj.name = 'Bob'; // 静默失败(严格模式下报错)
console.log(obj.name); // "Alice"
// 嵌套对象仍可变
obj.address = { city: 'Beijing' };
obj.address.city = 'Shanghai'; // 仍可修改2. 深冻结
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).forEach(prop => {
if (typeof obj[prop] === 'object' && obj[prop] !== null) {
deepFreeze(obj[prop]);
}
});
return Object.freeze(obj);
}
const obj = {
name: 'Alice',
address: { city: 'Beijing' }
};
deepFreeze(obj);
obj.address.city = 'Shanghai'; // 无法修改3. 扩展运算符
// 数组
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // 新数组
// 对象
const obj = { name: 'Alice', age: 30 };
const newObj = { ...obj, age: 31 }; // 新对象4. Array 方法
// map - 返回新数组
const doubled = [1, 2, 3].map(x => x * 2);
// filter - 返回新数组
const evens = [1, 2, 3, 4].filter(x => x % 2 === 0);
// slice - 返回新数组
const copy = [1, 2, 3].slice();
// concat - 返回新数组
const merged = [1, 2].concat([3, 4]);💡 实际应用
1. React 状态更新
// ❌ 错误:直接修改状态
this.state.items.push(newItem);
// ✅ 正确:创建新数组
this.setState({
items: [...this.state.items, newItem]
});2. Redux 状态管理
// Redux reducer 必须是纯函数
function todosReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload]; // 返回新数组
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id
? { ...todo, completed: !todo.completed }
: todo
);
default:
return state;
}
}3. 嵌套对象更新
// 更新嵌套对象
const user = {
name: 'Alice',
profile: {
age: 30,
address: { city: 'Beijing' }
}
};
// 使用扩展运算符
const updatedUser = {
...user,
profile: {
...user.profile,
address: {
...user.profile.address,
city: 'Shanghai'
}
}
};🔧 工具库
1. Immer
import produce from 'immer';
const state = {
users: [
{ id: 1, name: 'Alice' }
]
};
const nextState = produce(state, draft => {
draft.users.push({ id: 2, name: 'Bob' });
draft.users[0].name = 'Alice Updated';
});2. Immutable.js
import { List, Map } from 'immutable';
const list = List([1, 2, 3]);
const newList = list.push(4); // 返回新 List
const map = Map({ name: 'Alice', age: 30 });
const newMap = map.set('age', 31); // 返回新 Map⚠️ 注意事项
1. 性能考虑
// 大对象/数组的深拷贝可能影响性能
// 考虑使用 Immutable.js 等库的结构共享2. 浅拷贝陷阱
const obj = {
items: [1, 2, 3]
};
const newObj = { ...obj };
newObj.items.push(4); // 修改了原对象的 items
// 需要深拷贝
const newObj = {
...obj,
items: [...obj.items]
};3. 引用比较
// 不可变数据可以安全地进行引用比较
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false(不同引用)
// 如果数据未变,引用应该相同
const arr3 = arr1;
console.log(arr1 === arr3); // true(相同引用)🔗 相关链接
参考: