1、深拷贝和浅拷贝的定义
- ** 浅拷贝:**
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- ** 浅拷贝:**
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
2、一行的深拷贝
JSON.parse(JSON.stringify(json));
- 如果json里面有时间对象,则序列化结果:时间对象=>字符串的形式;
- 如果json里有RegExp、Error对象,则序列化的结果将只得到空对象 RegExp、Error => {};
- 如果json里有 function,undefined,则序列化的结果会把 function,undefined 丢失;
- 如果json里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
- 如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor;
- 如果对象中存在循环引用的情况也无法实现深拷贝
3、基础版
考虑到我们要拷贝的对象是不知道有多少层深度的,我们可以用递归来解决问题,代码如下:
- 如果是原始类型,无需继续拷贝,直接返回
- 如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。
// 判断是否为引用类型
function isObject(target){
const type = typeof target;
retrun target !==null && (type === 'object' || type === 'function');
}
// 初始化数据
function getInit(target){
const Ctor = taret.constructor
return new Ctor()
}
// 实现深拷贝
function clone(target){
// 如果是原始类型 直接返回
if(!isObject(target)) return target;
// 初始化数据类型
let cloneTarget = getInit(target);
if(const key in target){
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
}
很简单吧,但是当遇以下测试用例情况,深拷贝的话会出现问题!!!
const target = {
field1: 1,
field2: undefined,
field3: [1,2,3],
field4: {
child: 'child',
child2: {
child2: 'child2'
}
}
};
target.target = target;
如果深拷贝以上代码,由于是循环引用递归就会进入死循环导致栈内存溢出了。
4、解决循环引用
循环引用,既对象的属性间接或者直接的引用了自身的情况,循环引用会因为递归进入死循环导致栈内存溢出。
解决循环引用问题,我们可以额外开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝过这个对象,如果有的话直接返回,如果没有的话继续拷贝,这样就巧妙化解的循环引用的问题。
这个存储空间,需要可以存储** key-value ** 形式的数据,且** key ** 可以是一个引用类型,我们可以选择** Map或者WeakMap ** 这种数据结构:
Map与WeakMap的对比
-
Map对象的键可以是任何类型,但WeakMap对象中的键只能是对象引用
-
Map是强引用对象,WeakMap是弱引用对象(WeakMap弱引用的只是键名,而不是键值。键值依然是正常引用。)
** 何为弱引用对象:**
在计算机程序设计中,弱引用与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。 一个对象若只被弱引用所引用,则被认为是不可访问(或弱可访问)的,并因此可能在任何时刻被回收。
我们默认创建一个对象:const obj = {},就默认创建了一个强引用的对象,我们只有手动将obj = null,它才会被垃圾回收机制进行回收,如果是弱引用对象,垃圾回收机制会自动帮我们回收。
如果我们要拷贝的对象非常庞大时,使用** Map ** 会对内存造成非常大的额外消耗,而且我们需要手动清除** Map ** 的属性才能释放这块内存,而** WeakMap ** 会帮我们巧妙化解这个问题。
解决循环引用的操作步骤
- 检查map中有无克隆过的对象
- 有 - 直接返回
- 没有 - 将当前对象作为** key ** ,克隆对象作为** value ** 进行存储,继续克隆。
// 判断是否为引用类型
function isObject(target){
const type = typeof target;
retrun target !==null && (type === 'object' || type === 'function');
}
// 初始化数据
function getInit(target){
const Ctor = taret.constructor
return new Ctor()
}
// 实现深拷贝
function clone(target, map = new WeakMap()){
// 如果是原始类型 直接返回
if(!isObject(target)) return target;
// 初始化数据类型
let cloneTarget = getInit(target);
// 解决循环引用问题
if(map.get(target)){
return map.get(target)
}
map.set(target, cloneTarget)
if(const key in target){
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
}
5、处理其他数据类型
可继续遍历类型
上面我们已经考虑的object、array都属于可以继续遍历的类型,因为它们内存都还可以存储其他数据类型的数据,另外还有Map,Set等都是可以继续遍历的类型,这里我们只考虑这四种,
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const arrayTag = '[object Object]';
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
cont deepTag = [mapTag,setTag,arrayTag,arrayTag]
// 判断是否为引用类型
function isObject(target){
const type = typeof target;
retrun target !==null && (type === 'object' || type === 'function');
}
// 初始化数据
function getInit(target){
const Ctor = taret.constructor
return new Ctor()
}
// 判断类型
function getType(target) {
return Object.prototype.toString.call(target);
}
// 实现深拷贝
function clone(target, map = new WeakMap()){
// 如果是原始类型 直接返回
if(!isObject(target)) return target;
// 初始化数据类型
let type = getType(target)
let cloneTarget
if(deep includes type){
cloneTarget = getInit(target); // 可继续遍历类型
}
// 解决循环引用问题
if(map.get(target)){
return map.get(target)
}
map.set(target, cloneTarget)
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value,map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value,map));
});
return cloneTarget;
}
if(const key in target){
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
}
下面我们继续处理其他类型:
不可继续遍历类型
其他剩余的类型我们把它们统一归类成不可处理的数据类型,我们依次进行处理: Bool、Number、String、String、Date、Error这几种类型我们都可以直接用构造函数和原始数据创建一个新对象:
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const arrayTag = '[object Object]';
const argsTag = '[object Arguments]';
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const funcTag = '[object Function]'
cont deepTag = [mapTag,setTag,arrayTag,arrayTag]
// 判断是否为引用类型
function isObject(target){
const type = typeof target;
retrun target !==null && (type === 'object' || type === 'function');
}
// 初始化数据
function getInit(target){
const Ctor = taret.constructor
return new Ctor()
}
// 判断类型
function getType(target) {
return Object.prototype.toString.call(target);
}
// 初始化不可遍历数据类型
function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe);
default:
return null;
}
}
// 克隆Symbol类型:
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
// 克隆正则:
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
// 克隆函数:
function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
console.log('普通函数');
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 {
return eval(funcString);
}
}
// 实现深拷贝
function clone(target, map = new WeakMap()){
// 如果是原始类型 直接返回
if(!isObject(target)) return target;
// 初始化数据类型
let type = getType(target)
let cloneTarget
if(deepTag includes type){
cloneTarget = getInit(target); // 可继续遍历类型
} else {
cloneTarget = cloneOtherType(target,type); // 不可继续遍历类型
}
// 解决循环引用问题
if(map.get(target)){
return map.get(target)
}
map.set(target, cloneTarget)
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value,map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value,map));
});
return cloneTarget;
}
if(const key in target){
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
}
最后 最后
为了更好的阅读,我们用一张图来展示上面所有的代码: