通过本文:你将学会一个较为完善的深拷贝实现方案,以及下面五种优化方案:
数组对象
的深拷贝- 对
set
,map
类型的深拷贝处理 - 对
函数
类型的处理 - 对
symbol
类型的处理(作为值
或者作为key
时) - 循环引用导致调用栈爆掉的解决方式
深拷贝是什么?
深拷贝是指创建一个新对象,并将原始对象中的所有属性复制到新对象中,包括嵌套对象和数组,以确保两个对象不再有任何关系,不会相互影响,修改新对象不会影响原始对象。
方法一:JSON.parse(JSON.stringify(info))
缺点:无法对函数
,Symbol
,对象
的循环引用进行拷贝
PS:面试官想看的肯定也不是这个
javascript
const obj3 = JSON.parse(JSON.stringify(info))
方法二:递归实现基础的对象类型深拷贝
下方代码实现对象类型的深拷贝
js
// 深拷贝方法
function deepClone(value) {
// 如果不是引用数据类型,直接返回其本身
if (!isReferenceType(value)) return value;
// 是对象就创建新对象,去递归循环拷贝这个对象中不是对象的属性
const newObj = {};
for (const key in value) {
newObj[key] = deepClone(value[key]);
}
return newObj
}
const deepObj = deepClone(info);
console.log(deepObj);
js
// 检测是否是引用数据类型,函数、对象、数组
function isReferenceType(value) {
const valueType = typeof value;
return (valueType !== null && (valueType === "object" || valueType === "function"));
}
优化一:数组对象深拷贝
如果传入的是一个数组对象类型呢?
js
const newObj = {};
// 将上方代码替换为下方代码
const newObj = Array.isArray(value) ? [] : {};
js
function deepClone(value) {
if (!isReferenceType(value)) return value;
const newObj = Array.isArray(value) ? [] : {};
for (const key in value) {
newObj[key] = deepClone(value[key]);
}
return newObj
}
// 测试数据
const books = [
{
name: "你不知道的JavaScript",
price: 40,
info: { text: "无" },
buyName: [1, 2, 3],
},
{ name: "JavaScript高级程序设计", price: 60 },
{ name: "vue3开发实战", price: 50 },
];
const deepValue = deepClone(books);
console.log(deepValue);
可以看到是拷贝成功的
优化二:对set,map类型处理
js
// 如果是set类型
if (value instanceof Set) {
const newSet = new Set();
for (const setItem of value) {
newSet.add(deepClone(setItem));
}
return newSet;
}
// 如果是map类型
if (value instanceof Map) {
const newMap = new Map();
for (const [key, v] of value) {
newMap.set(key, deepClone(v))
}
return newMap
}
js
function deepClone(value) {
// 1.如果不是对象,直接返回其本身
if (!isReferenceType(value)) return value;
// 2.如果是set类型
if (value instanceof Set) {
const newSet = new Set();
for (const setItem of value) {
newSet.add(deepClone(setItem));
}
return newSet;
}
// 3.如果是map类型
if (value instanceof Map) {
const newMap = new Map();
for (const [key, v] of value) {
newMap.set(key, deepClone(v))
}
return newMap
}
// 4.如果是对象就创建新对象,去递归循环拷贝这个对象中不是对象的属性
const newObj = Array.isArray(value) ? [] : {};
for (const key in value) {
newObj[key] = deepClone(value[key]);
}
return newObj;
}
// 测试数据
const set1 = new Set([4,5,6])
const map1 = new Map()
const map2 = new Map()
map1.set('key1', 'value1')
map1.set('key2', 'value2')
map2.set('key22', 'value22')
map1.set('key3', map2)
const books = [
{
name: "你不知道的JavaScript",
price: 40,
info: { text: "无" },
buyName: [1, 2, 3],
},
{ name: "JavaScript高级程序设计", price: 60 },
{ name: "vue3开发实战", price: 50 },
set1,
map1
];
const deepValue = deepClone(books);
console.log(deepValue);
map2.set('key22', 'value2222')
可以看到对set
和map
类型都进行了深拷贝,在修改map2
的key2
值时,深拷贝之后的值也没有发生改变
优化三:对函数类型,不需要深拷贝
函数是作为功能存在的,一般不需要对其进行拷贝,只需要调用即可,拷贝还会浪费我们的内存空间,所以在这里对函数类型不做拷贝处理
js
if(typeof value === 'function') return value
优化四:symbol类型的深拷贝
你可能会感到奇怪,symbol
不是唯一的数据类型吗,哪里来的深拷贝处理,请看下方代码:
js
const symbolDemo = Symbol('123')
const obj1 = {k1: symbolDemo}
const obj2 = {k1: symbolDemo}
是不是理解了,这里指的是在引用类型的情况下的处理
分两种情况:
- 值是symbol类型
- key是symbol类型
js
if(typeof value === 'symbol') {
return Symbol(value.description)
}
js
const newObj = Array.isArray(value) ? [] : {};
// 基本数据类型作为key时
for (const key in value) {
newObj[key] = deepClone(value[key]);
}
// 对symbol作为key时的单独处理
const symbolKeys = Object.getOwnPropertySymbols(value)
for (const symbolKey of symbolKeys) {
newObj[Symbol(symbolKey.description)] = deepClone(value[symbolKey])
}
return newObj;
}
优化五:循环引用的处理
解决对象内部属性自己引用自己导致调用栈爆掉的问题
js
const info = {
name: "xiaobai",
age: 18,
friend: {
name: "ming",
age: 19,
friend: {
name: "xiaobai",
sex: "男",
},
},
};
// 自己引用自己
info.self = info
可以看到报错:超过最大调用栈的大小
解决方式如下:
使用weakmap
来记录有没有创建过该key
,如果有创建过,直接使用上一次传入的值;
ps:WeakMap是弱引用,方便销毁
MDN:
通过传入第二个参数,给一个默认值map
,每次函数执行完都会销毁,不会损耗内存空间
结果:成功解决
全部代码
js
function deepClone(value, map = new WeakMap()) {
// 对symbol类型的拷贝
if (typeof value === "symbol") {
return Symbol(value.description);
}
// 1.如果不是对象,直接返回其本身
if (!isReferenceType(value)) return value;
// 2.如果是set类型
if (value instanceof Set) {
const newSet = new Set();
for (const setItem of value) {
newSet.add(deepClone(setItem));
}
return newSet;
}
// 3.如果是map类型
if (value instanceof Map) {
const newMap = new Map();
for (const [key, v] of value) {
newMap.set(key, deepClone(v));
}
return newMap;
}
// 4.如果是函数类型,则不对其进行拷贝
if (typeof value === "function") return value;
// 5.如果是对象就创建新对象,去递归循环拷贝这个对象中不是对象的属性
// 解决循环引用的问题,使用weakmap记录
if (map.get(value)) return map.get(value);
const newObj = Array.isArray(value) ? [] : {};
map.set(value, newObj);
// 基本数据类型作为key时
for (const key in value) {
newObj[key] = deepClone(value[key], map);
}
// 对symbol作为key时的单独处理
const symbolKeys = Object.getOwnPropertySymbols(value);
for (const symbolKey of symbolKeys) {
newObj[Symbol(symbolKey.description)] = deepClone(value[symbolKey], map);
}
return newObj;
}