读大神ConardLi写的《如何写出一个惊艳面试官的深拷贝?》自己的记录,原文将深拷贝讲的非常仔细非常全面,推荐收藏学习。
主要概念
浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
深拷贝实现要考虑的几个问题
1. 拷贝对象为原始类型
2. 拷贝对象包含多层
使用递归方法解决。
如果是原始类型,无需继续拷贝,直接返回
如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。
3. 拷贝对象为数组
4. 循环引用
对象的属性间接或直接的引用了自身
const obj = {};
obj.self = obj;
obj === obj.self; // true
循环引用这块有个疑问
let target = {name: 'target'};
target.myself = target;
let copy = deepClone(target); // 深拷贝target对象
copy.name = 'copy';
问:按照深拷贝的概念,copy.myself.name === 'copy' 还是 'target' ?
5. 性能优化
使用while循环优化
6. 其他引用类型function
, null
, Map
, Set
源代码
function deepClone(source, map = new WeakMap()) {
if (typeof source === 'object' && source !== null){
const type = Object.prototype.toString.call(source);
// 使用原对象的构造方法,可以保留对象原型上的数据
const Ctor = source.constructor;
let cloneTarget = new Ctor();
// 防止循环引用
if(map.get(source)){
return source;
}
map.set(source, cloneTarget);
// 拷贝Set
if(type === "[object Set]"){
source.forEach(value => {
cloneTarget.add(deepClone(value));
});
return cloneTarget;
}
// 拷贝Map
if(type === "[object Map]"){
source.forEach((value, key) => {
cloneTarget.set(key, deepClone(value));
});
return cloneTarget;
}
const isArray = Array.isArray(source);
const keys = isArray ? source : Object.keys(source);
let index = 0;
while(index < keys.length){
let key = isArray ? index : keys[index]
if(typeof cloneTarget[key] === 'object') {
cloneTarget[key] = deepClone(source[key], map);
} else {
cloneTarget[key] = source[key];
}
index++;
}
return cloneTarget;
} else if (typeof source === 'function'){
return cloneFunction(source);
} else {
return source;
}
}
function cloneFunction(func){
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
// 通过prototype来区分下箭头函数和普通函数,箭头函数是没有prototype
if (func.prototype) {
// 使用正则取出函数体和函数参数,然后使用new Function ([arg1[, arg2[, ...argN]],] functionBody)
// 构造函数重新构造一个新的函数
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
console.log('匹配到函数体:', body[0]);
if (param) {
const paramArr = param[0].split(',');
console.log('匹配到参数:', paramArr);
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
// 使用eval和函数字符串来重新生成一个箭头函数
return eval(funcString);
}
}
// 测试用例
obj = {
boo: true,
name: 'sunny',
id: 1001,
map: new Map(),
params: {limit: 1000, query: {name: 'aaa'}},
getName: function (){
return this.name
}
}
obj.myself = obj
copy = deepClone(obj)