blog
blog copied to clipboard
关于JavaScript的深拷贝
关于浅复制很容易,想必大家都清楚,比如Object.assign()
亦或者是利用ES6的扩展运算符(object spread) ,之前也有总结过一部分,最近看了一些文章,所以这次我们主要来聊聊深复制
Recursion
function deepCopy(p,c){
var i;
c = c||{};
for(i in p){
if(p.hasOwnProperty(i)){
if(typeof p[i]==="object"){
c[i] = Array.isArray(p[i])?[]:{};
deepCopy(p[i],c[i]);
}else{
c[i] = p[i];
}
}
}
return c;
}
上述代码在复制时会进行类型检查 如果复制对象为一个数组或者对象会递归的遍历属性对象并将属性元素复制出来,有个问题就是无法解决循环引用
const x = {};
const y = {x};
x.y = y;
const copy = deepCopy(x) // Uncaught RangeError: Maximum call stack size exceeded
JSON.parse
算是一种比较老的方法,通过将对象字符串化然后解析转化回对象,可能看上去有些头重脚轻,但是确实有效
const obj = /* ... */;
const copy = JSON.parse(JSON.stringify(obj));
关于此方法同样有一个潜在的缺陷是无法处理对象中的循环引用
const x = {};
const y = {x};
x.y = y;
const copy = JSON.parse(JSON.stringify(x)); //Uncaught TypeError: Converting circular structure to JSON
还有一点是它无法用于处理JS的一些内置类型比如 Maps,Sets,RegExps,Dates,ArrayBuffers...等
MessageChannel
利用postMessage API 可以解决上述JSON.parse无法解决的循环引用,和无法处理内置类型的问题
function structuralClone(obj){
return new Promise(resolve ={
const {port1,port2} = new MessageChannel();
port2.onmessage = ev=> resolve(ev.data);
port1.postMessage(obj);
});
}
const obj = /* ... */;
const copy = await structuralClone(obj);
这个方法有一个缺点就是它是异步的,虽然不是什么大问题,但是有时候你可能需要的是一个同步的深复制办法
History API
如果你用过history.pushState()来构建SPA应用的话,那么你肯定知道你可以通过提供一个state对象来存储URL,并且它是同步的,这里我们为了避免带来其他不必要的麻烦和问题,在使用完state后记得还原它,同时我们用history.replaceState()来代替history.pushState()
function structuralClone(obj){
const oldState = history.state;
history.replaceState(obj,document.title);
const copy = history.state;
history.replaceState(oldState,document.title);
return copy;
}
const obj = /* ... */;
const copy = structuralClone(obj);
tips: Safari浏览器对replaceState 做出了限制一个窗口30s内调次数不超过100次
Notification API
利用Notification API深复制对象
function structuralClone(obj){
return new Notification('',{data:obj,slient:true}).data;
}
const obj = /* ... */;
const copy = structuralClone(obj);
tips: Safari 由于某些原因 总是返回undefined
Conclusion
- 如果你不用考虑循环引用问题和处理JS内置的一些类型的问题,需要兼容不同的浏览器并考虑速度的话,推荐使用JSON.parse(JSON.stringify())
- 如果不确定因素多,那么MessageChannel更可靠一些
while("true"){ console.log("大哥666666"); }
MessageChannel那里能给出下例子吗,我试了自己创建例子出错
postMessage我试了下,貌似和JSON方法一样不能拷贝函数,查了文章,好像只有结构化数据可以被拷贝 https://stackoverflow.com/questions/46145403/failed-to-execute-postmessage-on-serviceworker-function-could-not-be-cloned https://stackoverflow.com/questions/27558398/datacloneerror-the-object-could-not-be-cloned-in-firefox-34