1. 利用JSON實(shí)現(xiàn)
JSON.parse(JSON.stringify(obj))
問題:
- Date()類型會(huì)變成了字符串
- 會(huì)丟失值為undefined或函數(shù)的屬性
- 會(huì)丟失鍵或值為Symbol類型的屬性
- 正則會(huì)變成{}
- 有互相嵌套的對(duì)象會(huì)報(bào)錯(cuò) Uncaught TypeError: Converting circular structure to JSON
2. 遞歸實(shí)現(xiàn)深拷貝
第一步
遇到類型為object的屬性時(shí),遞歸調(diào)用。
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;
}
問題:
- 對(duì)數(shù)組使用typeof操作符也會(huì)得到'object',導(dǎo)致數(shù)組會(huì)拷貝成了對(duì)象。例如[0,1,2]會(huì)變成{0:0, 1:1, 2:2}
- for in 循環(huán)不會(huì)枚舉出Symbol類型的鍵,使得拷貝結(jié)果缺少Symbo類型鍵的屬性
- 未處理互相嵌套的情況
第二步
處理數(shù)組
const result = Array.isArray(obj) ? [] : {};
第三步
處理Symbol類型的鍵
Object.getOwnPropertySymbols() 方法返回一個(gè)給定對(duì)象自身的所有 Symbol 屬性的數(shù)組
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處理互相嵌套的對(duì)象,當(dāng)map已經(jīng)存放了對(duì)象的時(shí)候,直接返回,不繼續(xù)遞歸下去,避免無(wú)限遞歸導(dǎo)致棧溢出。
最終版本:
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. 循環(huán)實(shí)現(xiàn)深拷貝
利用一個(gè)棧輔助進(jìn)行深度優(yōu)先遍歷。其他細(xì)節(jié)與遞歸實(shí)現(xiàn)類似。
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) {
//將當(dāng)前處理的節(jié)點(diǎn)和父節(jié)點(diǎn)關(guān)聯(lián)起來(lái)
curNode = parent[key] = Array.isArray(value) ? [] : {};
}
if(set.has(value)){
//如果這個(gè)對(duì)象之前已經(jīng)出現(xiàn)過(guò) 就不處理 防止無(wú)限循環(huán)
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;
}
附測(cè)試用例
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);