LiuL0703 / blog

https://liul0703.github.io
38 stars 11 forks source link

关于JavaScript的深拷贝 #19

Open LiuL0703 opened 6 years ago

LiuL0703 commented 6 years ago

关于浅复制很容易,想必大家都清楚,比如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

14glwu commented 6 years ago

while("true"){ console.log("大哥666666"); }

14glwu commented 6 years ago

MessageChannel那里能给出下例子吗,我试了自己创建例子出错

BryanAdamss commented 6 years ago

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