手写随笔:1. 深拷贝
HardenSG opened this issue · comments
SG commented
深拷贝
对于数据类型大体上可以区分为基础类型和引用类型
-
那么对于基础类型来说,将一个变量赋给另一个变量实际上赋给的是基础类型的值,他们处于栈内存中两块地址风马牛不相及,所以并不会互相影响,这为浅拷贝
-
对于引用类型来说,变量是处于栈内存中,对象是处于堆内存中,所以实际上在栈中存储的是堆内存中的内存地址。因此变量之间相互赋值的其实是对象的地址。
-
那么显然两个对象变量的地址赋值是一个浅拷贝,那么在操作某一个变量的时候实际上是操作的同一个对象。某些情况下这不是我们想要看到的场景,那么可以实行深拷贝操作,或者践行不可变数据 immutable,但在这个小结不是讨论的主题。
所以需要创建出一个新的对象,该对象是与被拷贝对象相互独立的两个内存空间
代码实现
const TYPE_ENUM = {
"[object Object]": Object,
"[object Array]": Array,
"[object Map]": Map,
"[object Set]": Set,
};
const isObject = (v) => v !== null && typeof v === "object";
const getType = (v) => Object.prototype.toString.call(v);
const initCopied = (v) =>
Object.keys(TYPE_ENUM).find((o) => v === o) && TYPE_ENUM[v];
const _recursionClone = (target, map = new Map()) =>
!isObject(target)
? target
: (() => {
//// 获取类型
//// 初始化对象
//// 这样做的目的是保留类构造上的属性或方法
const T = getType(target);
const copied = new (initCopied(T))();
//// 处理循环引用
if (map.has(target)) {
return map.get(target);
}
map.set(target, copied);
//// 处理Map & Set
if (T === "[object Map]") {
target.forEach((o, k) => {
copied.set(k, _recursionClone(o, map));
});
return copied;
}
if (T === "[object Set]") {
target.forEach((o) => {
copied.add(_recursionClone(o, map));
});
return copied;
}
//// 对 对象做迭代并递归获得值
for (const k in target) {
const o = target[k];
copied[k] = _recursionClone(o, map);
}
return copied;
})();
步骤
- _recursionClone函数接收两个参数,首次调用的时候map是被赋为了默认值,map会把所有的对象属性缓存下来,这样在面临循环引用的问题的时候就直接将其返回出去即可,map帮我们解决了循环引用的问题
- 检测出是map或者set分别处理
- 对对象做迭代然后递归调用函数取得值
深拷贝结论
-
在面对简单的数据类型的时候,可以使用 JSON.parse(JSON.stringfy),但是无法处理函数的拷贝及循环引用等问题
-
如何解决循环引用问题?
- 循环引用是对象的属性不断地引用源对象这导致需要不断地调用递归的拷贝函数最终导致递归栈溢出
- 只需要将我们拷贝过的非基础类型的属性存储起来,这就需要一个 k-v 的数据结构将属性集存储起来,map 无疑是一个好的选择
SG commented
也可以看我手写时候的demo库和参照文章audition-demo【深拷贝】