1. 利用JSON实现
JSON.parse(JSON.stringify(obj))
问题:
- Date()类型会变成了字符串
- 会丢失值为undefined或函数的属性
- 会丢失键或值为Symbol类型的属性
- 正则会变成{}
- 有互相嵌套的对象会报错 Uncaught TypeError: Converting circular structure to JSON
2. 递归实现深拷贝
第一步
遇到类型为object的属性时,递归调用。
function cloneDeep(obj) {
if(obj == null){
return obj;
}
const result = {};
for(let key in obj){
if(obj.hasOwnProperty(key)){
if(typeof obj[key] == 'object'){
result[key] = cloneDeep(obj[key]);
}else{
result[key] = obj[key];
}
}
}
return result;
}
问题:
- 对数组使用typeof操作符也会得到'object',导致数组会拷贝成了对象。例如[0,1,2]会变成{0:0, 1:1, 2:2}
- for in 循环不会枚举出Symbol类型的键,使得拷贝结果缺少Symbo类型键的属性
- 未处理互相嵌套的情况
第二步
处理数组
const result = Array.isArray(obj) ? [] : {};
第三步
处理Symbol类型的键
Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组
const symbols = Object.getOwnPropertySymbols(obj);
for (let s of symbols) {
if (typeof obj[s] == 'object') {
result[s] = cloneDeep(obj[s]);
} else {
result[s] = obj[s];
}
}
第四步
利用map处理互相嵌套的对象,当map已经存放了对象的时候,直接返回,不继续递归下去,避免无限递归导致栈溢出。
最终版本:
function cloneDeep(obj, map = new WeakMap()) {
if (obj == null) {
return obj;
}
if (map.has(obj)) {
return map.get(obj);
}
const result = Array.isArray(obj) ? [] : {};
map.set(obj, result);
const symbols = Object.getOwnPropertySymbols(obj);
for (let s of symbols) {
if (typeof obj[s] == 'object') {
result[s] = cloneDeep(obj[s], map);
} else {
result[s] = obj[s];
}
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] == 'object') {
result[key] = cloneDeep(obj[key], map);
} else {
result[key] = obj[key];
}
}
}
return result;
}
3. 循环实现深拷贝
利用一个栈辅助进行深度优先遍历。其他细节与递归实现类似。
function cloneDeep(obj) {
const root = {};
const stack = [{
parent: root,
key: undefined,
value: obj
}];
const set = new WeakSet();
while (stack.length) {
const node = stack.pop();
const parent = node.parent;
const key = node.key;
const value = node.value;
let curNode = parent;
if (key !== undefined) {
//将当前处理的节点和父节点关联起来
curNode = parent[key] = Array.isArray(value) ? [] : {};
}
if(set.has(value)){
//如果这个对象之前已经出现过 就不处理 防止无限循环
parent[key] = value;
continue;
}else{
set.add(value);
}
const symbols = Object.getOwnPropertySymbols(value);
for (let s of symbols) {
if (typeof value[s] == 'object' && value[s] != null) {
stack.push({
value: value[s],
key: s,
parent: curNode
})
} else {
curNode[s] = value[s];
}
}
for (let k in value) {
if (value.hasOwnProperty(k)) {
if (typeof value[k] == 'object' && value[k] != null) {
stack.push({
parent: curNode,
key: k,
value: value[k]
});
} else {
curNode[k] = value[k];
}
}
}
}
return root;
}
附测试用例
let a = {
name: "luigi",
book: {
title: "hello",
price: "10"
},
a1: undefined,
a2: null,
a3: 123,
a4: [1, 2, 3, 4]
}
let s = Symbol('test');
a[s] = 'fuck';
var x = {
test: a
}
a.x = x;
let b = cloneDeep(a);
a.name = "lin";
a.book.price = "20";
console.log(a);
console.log(b);