2018.10.11
9096
男神苟哥
我们知道 js 对象是按共享传递(call by sharing)的,因此在处理复杂 js 对象的时候,往往会因为修改了对象而产生副作用
如果需要深拷贝,拷贝的时候判断一下属性值的类型,如果是对象,再递归调用深拷贝函数即可,具体实现可以参考 jQuery 的$.extend。实际上需要处理的逻辑分支比较多,在 lodash 中 的深拷贝函数 cloneDeep 甚至有上百行,那有没有简单粗暴点的办法呢?
最原始又有效的做法便是利用JSON.parse将该对象转换为其 JSON 字符串表示形式,然后将其解析回对象:
const deepClone(obj) => JSON.parse(JSON.stringify(obj)); 复制代码
对于大部分场景来说,除了解析字符串略耗性能外(其实真的可以忽略不计),确实是个实用的方法。但是尴尬的是它不能处理循环对象(父子节点互相引用)的情况,而且也无法处理对象中有 function、正则等情况。
MessageChannel 接口是信道通信 API 的一个接口,它允许我们创建一个新的信道并通过信道的两个 MessagePort 属性来传递数据
利用这个特性,我们可以创建一个MessageChannel,向其中一个 port 发送数据,另一个 port 就能收到数据了。
function structuralClone(obj) { return new Promise(resolve => { const {port1, port2} = new MessageChannel();
port2.onmessage = ev => resolve(ev.data);
port1.postMessage(obj);
});
} const obj = /* ... */ const clone = await structuralClone(obj); 复制代码
除了这样的写法是异步的以外也没什么大的问题了,它能很好的支持循环对象、内置对象(Date、 正则)等情况,浏览器兼容性也还行。但是它同样也无法处理对象中有 function的情况。
类似的 API 还有History API、Notification API等,都是利用了结构化克隆算法(Structured Clone) 实现传输值的。
如果需要频繁地操作一个复杂对象,每次都完全深拷贝一次的话效率太低了。大部分场景下都只是更新了这个对象一两个字段,其他的字段都不变,对这些不变的字段的拷贝明显是多余的。看看 Dan Abramov 大佬说的:
这些库的关键思路即是:创建持久化的数据结构(Persistent data structure),在操作对象的时候只 clone 变化的节点和其祖先节点,其他的保持不变,实现结构共享(structural sharing)。例如在下图中红色节点发生变化后,只会重新产生绿色的 3 个节点,其余的节点保持复用(类似软链的感觉)。这样就由原本深拷贝需要创建的 8 个新节点减少到只需要 3 个新节点了。
在Immutable.js中这里的 “节点” 并不能简单理解成对象中的 “key”,其内部使用了Trie(字典树)数据结构,Immutable.js会把对象所有的 key 进行 hash 映射,将得到的 hash 值转化为二进制,从后向前每 5 位进行分割后再转化为 Trie 树。
举个例子,假如有一对象 zoo:
zoo={ 'frog':