深拷贝和浅拷贝的区别?怎么实现深拷贝?
浅拷贝:只复制一层对象的属性【扩展运算符{...obj}
、Object.assign({}, obj)
】
数组的浅拷贝:
Array.from(arr)
、[…arr]
、『`arr.concat()`、`arr.slice()`』
深拷贝 :会递归地复制对象的所有层级的属性【递归函数、Json.parse(Json.stringify(obj))
】
Json.parse(Json.stringify(obj)) 局限:不能复制函数、循环引用
区别
-
『对象的属性是基本类型时』:深拷贝和浅拷贝都是拷贝新副本,拷贝后修改新对象不会影响原始对象;
-
对象的属性是『**引用类型**』时:浅拷贝只拷贝内存地址,不拷贝引用地址指向的数据 - 修改新对象会影响原始对象;深拷贝则会拷贝数据,修改新对象不会影响原始对象;
浅拷贝的实现
1.Object.assign():把任意多个的源对象自身的 可枚举属性 拷贝给目标对象,然后返回目标对象
2.Array.prototype.concat():合并两个或多个数组,不传参数时相当于浅拷贝
let arr = [1, 2, {username: 'aQiu'}];
let arr2 = arr.concat();
arr2[2].username = 'Alice';
console.log(arr); // [1, 2, {username: 'Alice'}] 变了
3.Array.prototype.slice(start, end):由 start 和 end 决定的数组的浅拷贝
深拷贝的实现
JSON.parse(JSON.stringify())
:用JSON.stringify
将对象转成JSON字符串,再用JSON.parse()
把字符串解析成对象,一去一来,新的对象产生了,可以实现数组或对象深拷贝,但不能处理函数、循环引用。
let arr = [1, 3, {username: 'aQiu'}];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = 'Alice';
console.log(arr); // [1, 3, {username: 'aQiu'}] 不变
- 【完整版本】递归:除了数组、对象,还要考虑 Date、正则RegExp、函数,以及对象的循环引用(简单的递归会导致栈溢出)问题处理
- 注意:
- ① weakMap 存储的值是对象 - 赋值地址后,
res
变化-map 中存储的值也变化了 - ② symbol不能使用 new,创建新的用
Symbol.for(target.description)
- ③ for…in 循环的是 object 的 key
- ① weakMap 存储的值是对象 - 赋值地址后,
// 更周全的深拷贝
function isObject(target) {
let type = typeof target;
return target !== null && (type === "object" || type === "function")
}
// Date、正则、Symbol 类型的特殊处理
function cloneSpecial(type, target) {
switch (type) {
case '[object Date]':
return new Date(target.getTime())
break;
case '[object RegExp]':
return new RegExp(target.source, target.flags);
break;
case '[object Symbol]':
// 对于Symbol类型,直接返回一个新的具有相同描述的Symbol
return Symbol.for(target.description);
break;
default:
let constructor = target.constructor;
return new constructor(target);
}
}
function deepCopy(target, map = new WeakMap()) {
// 原始类型 - 递归结束条件
if (!isObject(target)) {
return target
}
// 具体引用类型
let type = Object.prototype.toString.call(target);
let copy;
// deepCloneTypes 都是需要每一项一一处理的
let deepCloneTypes = ['[object Map]', '[object Set]', '[object Array]', '[object Object]'];
// Date、正则、Symbol 类型的特殊处理
if (!deepCloneTypes.includes(type)) {
return cloneSpecial(type, target);
}
// 初始化 - new 操作符
let constructor = target.constructor;
copy = new constructor();
// 防止循环
if (map.get(target)) {
return target;
}
map.set(target, copy);
// set
if (type === '[object Set]') {
target.forEach(it => {
copy.add(deepCopy(it, map));
});
return copy;
}
// map
if (type === '[object Map]') {
target.forEach((value, key) => {
copy.set(key, deepCopy(value, map));
});
return copy;
}
if (Array.isArray(target)) {
for (let i = 0; i < target.length; i++) {
copy[i] = deepCopy(target[i], map);
}
} else {
for (let i in target) {
if (target.hasOwnProperty(i)) {
copy[i] = deepCopy(target[i], map);
}
}
}
return copy;
}
// 循环引用
let obj1 = { name: 'Object 1' };
let obj2 = { name: 'Object 2' };
let array = [obj1, obj2];
obj1.reference = array;
let a = obj1;
console.log(typeof a, deepCopy(a));
- 【简单版本】递归:递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
// 利用 for 循环可以简单实现为
function deepCopy(target) {
// 判断对象是引用数据还是基础数据 - 基础数据直接返回
if (typeof target !== 'object' || target === null) {
return target
}
let copy;
if (Array.isArray(target)) {
copy = [];
for (let i = 0; i < target.length; i++) {
// 『通过 target[i] 遍历』
copy[i] = deepCopy(target[i]);
}
} else {
copy = {};
for (let i in target) {
if (target.hasOwnProperty(i)) {
// 『通过 target[i] 遍历』
copy[i] = deepCopy(target[i]);
}
}
}
return copy;
}
// 自己写的 - reduce 的问题在于 [[1,2,3],4,5,6] 数组嵌套数组时,被打平
// 数组
if (Array.isArray(target)) {
return target.reduce((pre, cur) => {
return pre.concat(deepCopy(cur))
}, []);
} else {
// 对象
return Object.keys(target).reduce((pre, cur) => {
return Object.assign(pre, { [cur]: deepCopy(target[cur]) })
}, {})
}