Immutable.js对象未复制问题:存入Map的对象受原对象变更影响
这个问题我当初刚用Immutable.js的时候也踩过一模一样的坑!其实核心原因是你误解了Immutable.js的处理逻辑——它不会自动对传入的引用类型做深克隆,而是做了一层浅包裹。
先还原你的问题场景
你写的代码大概是这样的:
const Immutable = require('immutable'); const originalObj = { name: 'Alice' }; // 把普通对象存入Immutable Map const userMap = Immutable.Map({ user: originalObj }); // 修改原对象 originalObj.name = 'Bob'; // 从Map取出的对象也变了 console.log(userMap.get('user').name); // 输出 'Bob',而非预期的'Alice'
为什么会这样?
Immutable.js的核心特性是自身的数据结构不可变——比如你调用userMap.set()时,它会返回一个全新的Map实例,原Map不会被修改。但它并不会递归地把你传入的普通对象、数组转成Immutable结构,只是把这些引用类型的地址存了起来。
也就是说,Map里的user键存储的只是originalObj的引用,而不是它的深克隆副本。所以当你修改原对象时,Map里指向的还是同一个内存地址的对象,自然会跟着变。
怎么解决这个问题?
有两种常见的处理方式:
1. 用Immutable.fromJS()递归转成Immutable结构
fromJS()会自动把传入的普通对象、数组递归转换成Immutable的Map、List,这样内部的所有层级都会变成不可变结构,原对象的修改不会影响它:
const Immutable = require('immutable'); const originalObj = { name: 'Alice' }; // 用fromJS代替Map,递归转成Immutable结构 const userMap = Immutable.fromJS({ user: originalObj }); originalObj.name = 'Bob'; // 此时取出的是Immutable Map,要用get()访问属性 console.log(userMap.getIn(['user', 'name'])); // 输出 'Alice' // 如果要修改Immutable里的数据,必须用它的API(比如setIn),会返回新的实例 const updatedMap = userMap.setIn(['user', 'name'], 'Charlie'); console.log(updatedMap.getIn(['user', 'name'])); // 输出 'Charlie'
2. 手动将单个对象转成Immutable Map再存入
如果你只需要处理单个层级的对象,也可以手动把普通对象转成Immutable Map后再存入:
const userMap = Immutable.Map({ user: Immutable.Map(originalObj) }); originalObj.name = 'Bob'; console.log(userMap.get('user').get('name')); // 输出 'Alice'
或者用深克隆(比如JSON.parse(JSON.stringify(originalObj)))把原对象复制一份再存入,但这种方法只适合无循环引用的简单对象,不如Immutable的原生API可靠。
总结一下
Immutable.js只保证自身的结构不可变,不会自动处理外部传入的引用类型。如果要让存入的对象也具备不可变性,必须显式地把它们转成Immutable的结构(用fromJS()或Map()/List()),而不是直接存入普通对象。
内容的提问来源于stack exchange,提问作者Cazineer

