947 字
5 分钟
手写系列-手写深拷贝

前言#

什么是深拷贝?

简单理解: b 是 a 的一份拷贝,b 中没有对 a 中对象的引用 另一种理解: b 是 a 的一份拷贝,a、b 各自画图,a 与 b 没有连接

简单的方法#

JSON 序列化与反序列化

let obj = {a:1,b:[2,3],c:{xx:4,yy:5}}
let obj2 = JSON.parse(JSON.stringify(obj))
obj2.a = 11
obj2.b[0] = 11
obj2.c.xx = 44
console.log(obj) // {a:1,b:[2,3],c:{xx:4,yy:5}} 原先的obj不会被改变

缺点:

  • 不支持函数
  • 不支持 undefind(因为 JSON 只有 null)
  • 不支持日期(会把 date 变成 iso8601 格式的日期字符串)
  • 不支持正则
  • 不支持 Error 对象
  • 不支持环状结构
  • NaN Infinity 会变成 null
const obj = {
a: "a",
b: undefined, // undefinde 丢失
c: function () {
// 函数丢失
console.log("this is a fnction");
},
d: NaN, // NaN 变为 null
e: Infinity, // Infinity 变为 null
f: new Date(), // 时间对象变为 iso8601格式字符串
g: /123/, // 正则 变为 空对象
h: new Error("error"), // 变为空对象
};
console.log(JSON.parse(JSON.stringify(obj)));
// {a: 'a',d:null,e: null,f: 'iso8601格式字符串',g:{},h: {}}
obj.i = obj;
console.log(JSON.parse(JSON.stringify(obj))); // 报错

自己实现一个深拷贝#

复制基本类型#

基本类型:#

  • Number
  • String
  • Boolean
  • Symbol
  • undefined
  • null

基本类型直接返回就好了

function deepClone(souce) {
return souce;
}

引用类型 递归克隆#

  • 普通对象#
function deepClone(source) {
if (source instanceof Object) {
const dist = new Object();
for (let key in source) {
dist[key] = deepClone(source[key]);
}
return dist;
} else {
return source;
}
}
  • Array 数组对象#

    如果是数组的话 用上面的方法去拷贝 返回的是一个伪数组 且不会拷贝 length 属性 所以我们的 dist 要是一个数组对象

function deepClone(source) {
if (source instanceof Object) {
if (source instanceof Array) {
// 数组
const dist = new Array();
for (let key in source) {
dist[key] = deepClone(source[key]);
}
return dist;
} else {
// 普通对象
const dist = new Object();
for (let key in source) {
dist[key] = deepClone(source[key]);
}
return dist;
}
} else {
return source;
}
}
  • Function 对象#

    函数的话 有两种方法 第一种就是再封装一个函数去调用源函数

if(source instance of Function){
const dist = function () {
return source.apply(this, arguments);
};
for (let key in source) {
dist[key] = deepClone(source[key]);
}
return dist;
}

但是这种方法不会拷贝 length 属性 也就是函数形参个数 可以通过正则匹配函数体 函数参数 然后重新生成函数

const sourceString = source.toString();
let body = sourceString.match(/(?<={)((\n*.*)*)(?=})/m)[0];
let params = sourceString.match(/(?<=\().*(?=\))/)[0];
dist = new Function(...params.split(","), body);
  • RegExp 正则对象#
else if (source instanceof RegExp) {
dist = new RegExp(source.source, source.flags);
for (let key in source) {
dist[key] = deepClone(source[key]);
}
return dist
}
  • Date 日期对象#
else if (source instanceof Date) {
dist = new Date(source);
for (let key in source) {
dist[key] = deepClone(source[key]);
}
return dist
}
  • 环结构#
a = { name: "a" };
a.self = a;

a 就是环状结构 可以通过缓存的方式去拷贝环结构 在拷贝 a.self 的时候我们已经拷贝过 a 了 可以将 a 与 a 的深克隆结果缓存起来

const cache = []
function deepClone(source){
if(source instanceof Object){
const sourceDist = cache.find((value) => value[0] === source)?.[1];
if (sourceDist) {
return sourceDist;
} else {
let dist
...
// 放在for 循环的上面 否则会进入死循环
cache.push([source,dist])
for (let key in source) {
dist[key] = deepClone(source[key]);
}
return dist
}
}
}

最终代码#

// 会有bug catch 没有清空 会有影响 可以用面向对象的方法解决
const cache = [];
module.exports = function deepClone(source) {
// 判断是不是对象
if (source instanceof Object) {
const sourceDist = cache.find((value) => value[0] === source)?.[1];
if (sourceDist) {
return sourceDist;
} else {
let dist;
// 数组
if (source instanceof Array) {
dist = new Array();
// 函数
} else if (source instanceof Function) {
// dist = function () {
// return source.apply(this, arguments);
// };
const sourceString = source.toString();
let body = sourceString.match(/(?<={)((\n*.*)*)(?=})/m)[0];
let params = sourceString.match(/(?<=\().*(?=\))/)[0];
dist = new Function(...params.split(","), body);
// 正则
} else if (source instanceof RegExp) {
dist = new RegExp(source.source, source.flags);
// 日期
} else if (source instanceof Date) {
dist = new Date(source);
// 普通对象
} else {
dist = new Object();
}
cache.push([source, dist]);
for (let key in source) {
// 跳过原型上的属性
if (source.hasOwnProperty(key)) {
dist[key] = deepClone(source[key]);
}
}
return dist;
}
// 基本类型
} else {
return source;
}
};
手写系列-手写深拷贝
https://blog.cxyxiaoyu.top/posts/手写系列-手写深拷贝/
作者
小宇
发布于
2020-09-13
许可协议
CC BY-NC-SA 4.0