开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情
前言
本文主要总结一些深拷贝,浅拷贝的方法总结和实现方式,如有写的不准确的地方,欢迎掘友们指出,相互学习,共同进步!在讲述之前建议大家先读一下这篇文章 JS数据类型有哪些,区别是什么?看这篇就够了!
一. 什么是浅拷贝什么是深拷贝?
浅拷贝:
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
简单来说意思就是:只拷贝该对象的基本数据,该对象的子对象不拷贝
深拷贝:
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
简单来说意思就是:会克隆出一个新对象,数据相同,但是引用地址不同
再附上一张图总该懂了吧!
二. 浅拷贝实现方式
function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}
function ShallowClone(originValue) {
if (!isObject(originValue)) {
return originValue
}
// 判断传入的对象是数组, 还是对象
const newObject = Array.isArray(originValue) ? []: {}
for (const key in originValue) {
newObject[key] = originValue[key]
}
return newObject
}
常用的方法
- Object.assigin
const newObj = {};
const obj = {
name: "hi",
info: {
age: 18,
address: {
city: "南京",
},
},
};
Object.assign(newObj, obj);
obj.info.age = 20;
obj.name = "ok";
console.log(newObj, obj);
- 扩展运算符(...)
const newObj = {...obj};
- Array.prototype.concat
数组的 concat 方法其实也是浅拷贝,使用场景比较少,使用concat连接一个含有引用类型的数组时,需要注意修改原数组中的元素的属性,因为它会影响拷贝之后连接的数组
const arr = [1, 2, { name: "hhh" }];
const newArr = arr.concat();
newArr[2].name = "ohmy";
- Array.prototype.slice
const arr = [1, 2, { name: "hhh" }];
const newArr = arr.slice();
注意:
它不会拷贝对象的继承属性;
它不会拷贝对象的不可枚举的属性;
可以拷贝 Symbol 类型的属性。
三. 深拷贝实现方式
基本实现:
function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}
function deepClone(originValue) {
// 判断传入的originValue是否是一个对象类型
if (!isObject(originValue)) {
return originValue
}
const newObject = {}
for (const key in originValue) {
newObject[key] = deepClone(originValue[key])
}
return newObject
}
复杂实现:
function isObject(value) {
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}
function deepClone(originValue, map = new WeakMap()) {
// 判断是否是一个Set类型
if (originValue instanceof Set) {
return new Set([...originValue])
}
// 判断是否是一个Map类型
if (originValue instanceof Map) {
return new Map([...originValue])
}
// 判断如果是Symbol的value, 那么创建一个新的Symbol
if (typeof originValue === "symbol") {
return Symbol(originValue.description)
}
// 判断如果是函数类型, 那么直接使用同一个函数
if (typeof originValue === "function") {
return originValue
}
// 判断传入的originValue是否是一个对象类型
if (!isObject(originValue)) {
return originValue
}
if (map.has(originValue)) {
return map.get(originValue)
}
// 判断传入的对象是数组, 还是对象
const newObject = Array.isArray(originValue) ? []: {}
map.set(originValue, newObject)
for (const key in originValue) {
newObject[key] = deepClone(originValue[key], map)
}
// 对Symbol的key进行特殊的处理
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const sKey of symbolKeys) {
// const newSKey = Symbol(sKey.description)
newObject[sKey] = deepClone(originValue[sKey], map)
}
return newObject
}
常用的方法
-
使用JSON.stringify()以及JSON.parse()
缺点:不可以拷贝
undefined , function, RegExp,Symbol等等类型,并且如果存在对象的循环引用,也会报错。
//将对象转换为json字符串形式,再将转换而来的字符串转换为原生js对象
const newObj = JSON.parse(JSON.stringify(obj))
- 第三方库实现( lodash.clonedeep)