不可变数据(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(相同引用)

🔗 相关链接


参考


javascript 不可变数据 immutability 函数式编程