先贴js fiddle代码地址:https://jsfiddle.net/warin2022/e0jpd9oc/125/
什么是深拷贝:
什么是浅拷贝:
// 浅拷贝
const arr = [1, 2, 3];
const shallowCopyArr = arr;
shallowCopyArr[1] = 4;
console.log(arr); // [1, 4, 3]
对于一个引用变量(Object、Array、Map、Set等):
浅拷贝拷贝完后,修改拷贝变量的属性,原变量也变化;
深拷贝拷贝完后,修改拷贝变量的属性,原变量不变化。
首先只考虑Object、Array:
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
这个基本能应付绝大部份情况了
接下来考虑没法stringify序列化的(Map、Set、Date、Regex、Symbol):
思路:
- primitive type原始类型直接拷贝就行
- reference type引用类型每种的处理方法不同:
- 首先通过Object.prototype.toString.call(obj)可以万能判断所有引用类型,比如:
- Object返回'[object Object]'
- Array返回'[object Array]'
- Map返回'[object Map]'
- ...
- 然后对各种引用类型分别处理,比如:
const obj = {...}; const copyObj = {}; for (const key in obj) { copyObj[key] = obj[key]; } const arr = [...]; const copyArr = []; for (const item in arr) { copyArr.push(item); }
- 注意以上value的地方是需要递归拷贝的(因为value可能也是一个引用类型),比如:
- copyObj[key] = deepClone(obj[key]);
- 首先通过Object.prototype.toString.call(obj)可以万能判断所有引用类型,比如:
代码:
function deepClone(obj) {
// 基础类型直接返回
if (typeof obj !== 'object' || obj === null) return obj;
// 引用类型获取其具体类型
let type = Object.prototype.toString.call(obj);
type = type.substr(8, type.length - 9); // '[object Object]'中截取'Object'
let newObj;
// 对每种引用类型进行各自的拷贝方式
if (type === 'RegExp') {
newObj = new RegExp(obj);
}
else if (type === 'Date') {
newObj = new Date(obj);
}
else if (type === 'Symbol') {
newObj = Symbol(obj.description);
}
else if (type === 'Map') {
newObj = new Map();
for (let [key, value] of obj) {
newObj.set(key, deepClone(value));
}
}
else if (type === 'Set') {
newObj = new Set();
for (let value of obj) {
newObj.add(deepClone(value));
}
}
else if (type === 'Array') {
newObj = [];
for (let value of obj) {
newObj.push(deepClone(value));
}
}
else if (type === 'Object') {
newObj = {};
for (let [key, value] of Object.entries(obj)) {
newObj[key] = deepClone(value);
}
}
return newObj;
}
最后加上循环引用的处理:
什么是循环引用:
const obj = {
a: 1,
};
obj.b = obj;
思路:
外面套一层闭包,用Map记录目前的引用对应
代码:
function deepClone(obj) {
const map = new Map(); // 记录引用
function baseClone(obj) {
// 基础类型直接返回
if (typeof obj !== 'object' || obj === null) return obj;
// 循环引用停止递归
if (map.has(obj)) return map.get(obj);
// 引用类型获取其具体类型
let type = Object.prototype.toString.call(obj);
type = type.substr(8, type.length - 9); // '[object Object]'中截取'Object'
let newObj;
// 对每种循环引用类型进行各自的拷贝方式
if (type === 'RegExp') {
newObj = new RegExp(obj);
map.set(obj, newObj); // 每次初始化newObj后记录引用
}
else if (type === 'Date') {
newObj = new Date(obj);
map.set(obj, newObj);
}
else if (type === 'Symbol') {
newObj = Symbol(obj.description);
map.set(obj, newObj);
}
else if (type === 'Map') {
newObj = new Map();
map.set(obj, newObj);
for (let [key, value] of obj) {
newObj.set(key, baseClone(value));
}
}
else if (type === 'Set') {
newObj = new Set();
map.set(obj, newObj);
for (let value of obj) {
newObj.add(baseClone(value));
}
}
else if (type === 'Array') {
newObj = [];
map.set(obj, newObj);
for (let value of obj) {
newObj.push(baseClone(value));
}
}
else if (type === 'Object') {
newObj = {};
map.set(obj, newObj);
for (let [key, value] of Object.entries(obj)) {
newObj[key] = baseClone(value);
}
}
return newObj;
}
return baseClone(obj);
}
测试:
let obj = {
a: 1,
b: 2n,
c: 'hello',
d: true,
e: Symbol('haha'),
f: null,
g: undefined,
h: {
h1: {
h2: 1,
h3: [
{
h4: [
2,
],
},
],
},
h5: 3,
},
i: [
1,
'a',
null,
],
j: new Date(),
k: /x/,
l: new Map([['l1', 1], ['l2', 2]]),
m: new Set(['m1', 'm2']),
};
obj.n = obj;
obj.o = {};
obj.o.o1 = obj;
obj.o.o2 = obj.n;
obj.o.o3 = obj.o;
obj.o.o4 = [obj.o.o3];
console.log(obj);
console.log(deepClone(obj));