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