手寫深拷貝
深拷貝
深拷貝簡(jiǎn)單理解就是b是a的一份拷貝再榄,且b中不存在a中對(duì)象的引用
深拷貝的實(shí)現(xiàn)
1.JSON序列化和反序列化
如果對(duì)象中全是基本類型狡刘,那么可以使用JSON.parse(JSON.stringify(a))
2.遞歸克隆
2.1 如果是普通類型,就直接拷貝
測(cè)試用例:
describe('deepClone', () => {
it('能夠復(fù)制基本類型number,string,boolean,undefined,null,symbol', () => {
const n = 123;
const n2 = deepClone(n);
assert(n === n2);
const s = '12345';
const s2 = deepClone(s);
assert(s === s2);
const b = true;
const b2 = deepClone(b);
assert(b === b2);
const u = undefined;
const u2 = deepClone(u);
assert(u === u2);
const empty = null;
const empty2 = deepClone(empty);
assert(empty === empty2);
const sym = Symbol();
const sym2 = deepClone(sym);
assert(sym === sym2);
});
});
代碼實(shí)現(xiàn):
function deepClone(source) {
return source;
}

此時(shí)我們能夠?qū)崿F(xiàn)基本類型的拷貝
2.2 如果是對(duì)象
2.2.1 普通對(duì)象
使用for in 遍歷對(duì)象上的屬性不跟,返回一個(gè)新的對(duì)象(注: for in 會(huì)遍歷原型上的屬性)
測(cè)試用例:
it('能夠復(fù)制對(duì)象', () => {
const obj1 = { name: 'sss', child: { name: 'sss-children' } };
const obj2 = deepClone(obj1);
assert(obj1 !== obj2);
assert(obj1.name === obj2.name);
assert(obj1.child !== obj2.child);
assert(obj1.child.name === obj2.child.name);
});
代碼實(shí)現(xiàn):
function deepClone(source) {
if (source instanceof Object) {
let dist = new Object();
for (let key in source) {
// for in會(huì)遍歷原型上的屬性
if (source.hasOwnProperty(key)) {
dist[key] = deepClone(source[key]);
}
}
return dist;
}
return source;
}

2.2.2 數(shù)組對(duì)象
使用new Array初始化
測(cè)試用例:
it('能夠復(fù)制數(shù)組對(duì)象', () => {
const array1 = [
[11, 12],
[21, 22],
[31, 32],
];
const array2 = deepClone(array1);
assert(array1 !== array2);
assert(array1[0] !== array2[0]);
assert(array1[1] !== array2[1]);
assert(array1[2] !== array2[2]);
assert.deepEqual(array1, array2);
});
代碼實(shí)現(xiàn):
function deepClone(source) {
if (source instanceof Object) {
let dist;
if (source instanceof Array) {
dist = new Array();
} else {
dist = new Object();
}
for (let key in source) {
// for in會(huì)遍歷原型上的屬性
if (source.hasOwnProperty(key)) {
dist[key] = deepClone(source[key]);
}
}
return dist;
}
return source;
}
2.2.3 函數(shù)對(duì)象
返回一個(gè)新的函數(shù),使用apply(this,arguments)
測(cè)試用例:
it('能夠復(fù)制函數(shù)', () => {
const f1 = function (x, y) {
return x + y;
};
f1.xxx = { yyy: { zzz: 1 } };
const f2 = deepClone(f1);
assert(f1 !== f2);
assert(f1.xxx !== f2.xxx);
assert(f1.xxx.yyy !== f2.xxx.yyy);
assert(f1.xxx.yyy.zzz === f2.xxx.yyy.zzz);
assert(f1(1, 2) === f2(1, 2));
});
代碼實(shí)現(xiàn):
function deepClone(source) {
if (source instanceof Object) {
let dist;
if (source instanceof Array) {
dist = new Array();
} else if (source instanceof Function) {
dist = function () {
return source.apply(this, arguments);
};
} else {
dist = new Object();
}
for (let key in source) {
// for in會(huì)遍歷原型上的屬性
if (source.hasOwnProperty(key)) {
dist[key] = deepClone(source[key]);
}
}
return dist;
}
return source;
}
2.2.4 日期和正則
日期使用new Date創(chuàng)建一個(gè)新的日期對(duì)象颓帝,正則使用new RegExp(source.source, source.flags)
測(cè)試用例:
it('可以復(fù)制正則', () => {
const reg1 = /hi\d+/gi;
reg1.xxx = { yyy: { zzz: 1 } };
const reg2 = deepClone(reg1);
assert(reg1.source === reg2.source);
assert(reg1.flags === reg2.flags);
assert(reg1 !== reg2);
assert(reg1.xxx !== reg2.xxx);
assert(reg1.xxx.yyy !== reg2.xxx.yyy);
assert(reg1.xxx.yyy.zzz === reg2.xxx.yyy.zzz);
});
it('可以復(fù)制日期', () => {
const date1 = new Date();
date1.xxx = { yyy: { zzz: 1 } };
const date2 = deepClone(date1);
assert(date1.source === date2.source);
assert(date1.flags === date2.flags);
assert(date1 !== date2);
assert(date1.getTime() === date2.getTime());
assert(date1.xxx !== date2.xxx);
assert(date1.xxx.yyy !== date2.xxx.yyy);
assert(date1.xxx.yyy.zzz === date2.xxx.yyy.zzz);
});
代碼實(shí)現(xiàn)
function deepClone(source) {
if (source instanceof Object) {
let dist;
if (source instanceof Array) {
dist = new Array();
} else if (source instanceof Function) {
dist = function () {
return source.apply(this, arguments);
};
} 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();
}
for (let key in source) {
// for in會(huì)遍歷原型上的屬性
if (source.hasOwnProperty(key)) {
dist[key] = deepClone(source[key]);
}
}
return dist;
}
return source;
}

目前我們粗略的實(shí)現(xiàn)了一個(gè)可以復(fù)制基本類型和常見(jiàn)對(duì)象的深拷貝
2.3 考慮環(huán)
const a = {}
a.self = a
測(cè)試用例:
it('環(huán)也能復(fù)制', () => {
const obj1 = { name: 'sss' };
obj1.self = obj1;
const obj2 = deepClone(obj1);
assert(obj1 !== obj2);
assert(obj1.name === obj2.name);
assert(obj1.self !== obj2.self);
});
此時(shí)我們直接運(yùn)行之前的代碼,發(fā)現(xiàn)控制臺(tái)直接報(bào)錯(cuò)了。因?yàn)閷?duì)象每次深拷貝之后會(huì)生成一個(gè)全新的對(duì)象,所以此時(shí)會(huì)造成遞歸不能終止购城。因此我們需要先判斷是否存在環(huán)吕座,然后進(jìn)行對(duì)象的拷貝。
我們創(chuàng)建一個(gè)cashe數(shù)組瘪板,里面存放origin和new
let cache = [];
function findCache(source) {
for (let i = 0; i < cache.length; i++) {
if (cache[i].origin && cache[i].origin === source) {
return cache[i].new;
}
}
return undefined;
}
function deepClone(source) {
if (source instanceof Object) {
let cachedDist = findCache(source);
if (cachedDist) {
return cachedDist;
} else {
let dist;
if (source instanceof Array) {
dist = new Array();
} else if (source instanceof RegExp) {
dist = new RegExp(source.source, source.flags);
} else if (source instanceof Function) {
dist = function () {
return source.apply(this, arguments);
};
} else if (source instanceof Date) {
dist = new Date(source);
} else {
dist = new Object();
}
cache.push({ origin: source, new: dist });
for (let key in source) {
// for in會(huì)遍歷原型上的屬性
if (source.hasOwnProperty(key)) {
dist[key] = deepClone(source[key]);
}
}
return dist;
}
}
return source;
}
