Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于JavaScript的深拷贝 #19

Open
LiuL0703 opened this issue Apr 2, 2018 · 3 comments
Open

关于JavaScript的深拷贝 #19

LiuL0703 opened this issue Apr 2, 2018 · 3 comments

Comments

@LiuL0703
Copy link
Owner

LiuL0703 commented Apr 2, 2018

关于浅复制很容易,想必大家都清楚,比如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更可靠一些
@14glwu
Copy link

14glwu commented Apr 13, 2018

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

@14glwu
Copy link

14glwu commented Apr 13, 2018

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

@BryanAdamss
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants