对象的深拷贝 上

482 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

前端程序员,可能没有谁没没复制对象吧? 今天就一天来学习学习对象的复制。

对象,非彼对象

为什么要进行复制

什么时候需要进项对象的复制??

  1. 不破坏原数据 原始数据可能还需要在别处使用,如果进行了更改,可能导致意外的结果。 比如函数的参数,构造函数的参数等等。

函数式编程中有一个重要的概念,副作用, 无副作用,其中有一条就是不要修改入参。

  1. 需要基于当前数据,创建新的数据,用于他用。

JSON.stringify + JSON.parse

如果全部是普通属性键,并且值类型没有function,symbol,Date,RegExp等等一些特殊对象, 或者类似 window这种循环引用的值。 其还是很有效的。 简单,暴力。

function clone(obj){
    return JSON.parse(JSON.stringify(obj))
}

clone({a:1,{b:2}}) // {a:1,{b:2}}

// 时间:转为字符串
clone({date: new Date()}) // {date: '2021-09-26T08:23:40.517Z'}

// 正则: 变为了空对象  异常
clone({regex: /[0-9]/ }) // {regex: {…}}
// Blob: 变为空对象, 异常
clone({blob:new Blob(["123"])}) // {blob: {…}}

// 函数
clone({fn(){}})  // {}
// wubdiw
clone({window}) // Uncaught TypeError: Converting circular structure to JSON

更多的限制,可以查看JSON.stringify()

简单说,就是对纯数据类型比较有用,比如服务端返回的接口数据这种。

BroadcastChannel等等

浏览器很多内置的API,进行通讯的时候,会对传递的参数进行复制,我们可以直接利用这个功能。比如我们使用BroadcastChannel来进行复制。

var ch1 = new BroadcastChannel("_____[clone]_____");
var ch2 = new BroadcastChannel("_____[clone]_____");

function clone(data){
    return new Promise((resolve)=>{    
        ch2.addEventListener("message", ev=>resolve(ev.data), {once: true});
        ch1.postMessage(data);
    })   
}

// 复制对象: 正确
var obj1 = {a:{b:1}};
clone(obj1).then(a=>console.log(a, a === obj1))  // {a:{b:1}} false

// 复制时间: 正确, 依旧能调用getFullYear方法
var obj1 = {
    a: new Date()
};
clone(obj1)
.then(a=>console.log(a, a === obj1, a.a.getFullYear()))
// {a: Sun Sep 26 2021 16:37:20 GMT+0800 (中国标准时间)} false 2021


// 复制正则, 正确,依旧能调用正则的test方法
var obj1 = {
    a:  /[0-9]/
};
clone(obj1)
.then(a=>console.log(a, a === obj1, a.a.test(1)))
// {a: /[0-9]/} false true


// 复制Blob, 正确,依旧还是Blob类型
var obj1 = {
    a: new Blob(["123"])
};
clone(obj1)
.then(a=>console.log(a, a === obj1, a.a.test(1)))
// {a: Blob} false '[object Blob]'


// 复制window
var obj1 = {
    a:1,
    window
};
clone(obj1).then(a=>console.log(a, a === obj1)) 
// Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'BroadcastChannel': #<Window> could not be cloned

// 复制函数
var obj1 = {
    a:1,
    fn(){}
};
clone(obj1).then(a=>console.log(a, a === obj1)) 
// Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'BroadcastChannel': fn(){} could not be cloned.

当然缺点,就是把同步变成了异步。

  1. function和window这中循环引用的依旧无法复制, 还会出异常,不过确实是一条路。
  2. 其对引用类型的复制,保持了原有的方法属性,这是比JSON.parse(JSON.stingify(obj))强大的地方。

其实, 其他消息通讯的API应该均是可以达到类似功能的,比如 window.postMessage, Storage的message事件,Web Worker等等。

小结

今天你收获了吗?