目录
操作三:深拷贝(JSON方法 : 这种深拷贝的方式对于函数、Symbol等是无法处理的)
额外内容:判断一个标识符是不是对象类型(手写高阶深拷贝方法需要使用此内容)
一、认识深拷贝与浅拷贝
1.浅拷贝与深拷贝的概念
在JavaScript中,浅拷贝和深拷贝是两个常用的对象复制方法。
浅拷贝是指创建一个新对象,然后将原对象的引用类型的属性复制给新对象。换句话说,新对象的引用类型属性和原对象的引用类型属性指向相同的内存地址。
深拷贝是指创建一个新对象,然后递归地将原对象的所有属性(包括引用类型属性)复制给新对象。换句话说,新对象和原对象的所有属性,包括引用类型属性,在内存中都是完全独立的。
2.浅拷贝与深拷贝的实现方法
浅拷贝的实现方式有多种,常见的有:
- Object.assign方法:使用Object.assign(target, source)将source对象的属性复制给target对象。
- 扩展运算符:使用扩展运算符({...obj})将对象obj的属性复制到一个新对象中。
深拷贝的实现方式也有多种,常见的有:
- JSON方法:使用JSON.parse(JSON.stringify(obj))将对象obj转换为JSON字符串,再通过JSON.parse方法将字符串转换为对象。需要注意的是,这种方法不能拷贝函数和特殊对象(如正则表达式、Date对象)。
- 递归方法:通过递归地复制对象的每个属性,包括引用类型属性,实现深拷贝。
需要注意的是,浅拷贝和深拷贝只针对引用类型的属性,对于原始类型的属性,复制的都是值。另外,对于循环引用的对象,使用深拷贝可能会导致堆栈溢出的问题,需要特别注意。
3.浅拷贝与深拷贝的区别
- 引用关系: 浅拷贝只复制对象的第一层属性,而深拷贝会递归复制所有嵌套对象。
- 影响原始对象: 浅拷贝修改复制对象的属性会影响原始对象,而深拷贝不会影响原始对象。
- 复制方式: 浅拷贝通常使用对象的引用复制方式,而深拷贝则通过递归复制对象及其所有子对象实现。
二 、如何实现浅拷贝与深拷贝
操作一:引用赋值
const info = {
name: "why",
age: 18,
friend: {
name: "kove"
},
running: function () { },
[Symbol()]: "abc",
}
info.obj = info
// 1.操作一:引用赋值
const obj1 = info
操作二:浅拷贝
const info = {
name: "why",
age: 18,
friend: {
name: "kove"
},
running: function () { },
[Symbol()]: "abc",
}
info.obj = info
//2. 操作二:浅拷贝
const obj2 = { ...info }
obj2.name = "james"
obj2.friend.name = "james"
console.log(info.friend.name)
const obj3 = Object.assign({}, info)
obj3.friend.name = "curry"
console.log(info.friend.name)
操作三:深拷贝(JSON方法 : 这种深拷贝的方式对于函数、Symbol等是无法处理的)
const info = {
name: "why",
age: 18,
friend: {
name: "kove"
},
running: function () { },
[Symbol()]: "abc",
}
info.obj = info
// 3.操作三:深拷贝
// 3.1. JSON方法 : 这种深拷贝的方式对于函数、Symbol等是无法处理的
const obj4 = JSON.parse(JSON.stringify(info))
obj4.friend.name = "blur"
console.log(info.friend.name)
console.log(obj4.friend.name)
console.log(obj4)
操作四:深拷贝(手写深拷贝函数(基本实现))
function deepCopy(originValue) {
// 1.如果是原始类型,直接返回
if (!isObject(originValue)) {
return originValue
}
// 2.如果是对象类型,才需要创建对象
const newObj = {}
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key])
}
return originValue
}
const info = {
name: "why",
age: 18,
friend: {
name: "kove",
address: {
name: "洛杉矶",
detail: "斯坦普斯中心"
}
}
}
const newObj = deepCopy(info)
info.friend.address.name = "北京市"
console.log(newObj)
额外内容:判断一个标识符是不是对象类型(手写高阶深拷贝方法需要使用此内容)
function isObject(value) {
// null,object,function,arr
const valueType = typeof value
return (value !== null) && (valueType === "object" || valueType === "function")
}
操作五:深拷贝中的数组拷贝
<script src="../js/is_object.js"></script>
function deepCopy(originValue) {
// 1.如果是原始类型,直接返回
if (!isObject(originValue)) {
return originValue
}
// 2.如果是对象类型,才需要创建对象
const newObj = Array.isArray(originValue) ? [] : {}
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key])
}
return originValue
}
const books = [
{ name: "黄金时代", price: 38.88, desc: { intro: "这本书不错", info: "这本书讲了一个很有意思的故事" } },
{ name: "假如给我三天光明", price: 39.99 },
{ name: "你不知道JavaScript", price: 99.00 }
]
const newBooks = deepCopy(books)
console.log(newBooks)
操作六:深拷贝的其他类型
<script src="../js/is_object.js"></script>
function deepCopy(originValue) {
//0.如果值是Symbol的类型
if (typeof originValue === "symbol") {
return Symbol(originValue.description)
}
// 1.如果是原始类型,直接返回
if (!isObject(originValue)) {
return originValue
}
// 2.如果是set类型
if (originValue instanceof Set) {
const newSet = new Set()
for (const setItem of originValue) {
newSet.add(deepCopy(setItem))
}
return newSet
}
// 3.如果是函数functin类型,不需要进行深拷贝
if (typeof originValue === "function") {
return originValue
}
// 4.如果是对象类型,才需要创建对象
const newObj = Array.isArray(originValue) ? [] : {}
//遍历普遍的key
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key])
}
// 单独遍历symbol
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const symbolKey of symbolKeys) {
newObj[symbolKey(symbolKey.description)] = deepCopy(originValue[symbolKey])
}
return newObj
}
const set = new Set(["abc", "cba", "nba"])
const s1 = Symbol()
const info = {
name: "why",
age: 18,
friend: {
name: "kove",
address: {
name: "洛杉矶",
detail: "斯坦普斯中心"
}
},
// 特殊类型:Set
set: set,
// 特性类型:function
running: function () {
console.log("running")
},
// 值的特殊类型: Symbol
symbolKey: Symbol("abc"),
// key是symbol时
[s1]: "aaaa",
[s2]: "bbbb"
}
const symbol = Symbol()
const newObj = deepCopy(info)
console.log(newObj)
console.log(newObj.symbolKey === info.symbolKey)
操作七:深拷贝中的循环引用
<script src="../js/is_object.js"></script>
// 检查是否为对象
function isObject(value) {
return typeof value === 'object' && value !== null;
}
// 深拷贝函数
function deepCopy(originValue, map = new WeakMap()) {
// 如果值是Symbol类型
if (typeof originValue === "symbol") {
return Symbol(originValue.description);
}
// 如果是原始类型,直接返回
if (!isObject(originValue)) {
return originValue;
}
// 如果是Set类型
if (originValue instanceof Set) {
const newSet = new Set();
for (const setItem of originValue) {
newSet.add(deepCopy(setItem, map));
}
return newSet;
}
// 如果是函数类型,不需要深拷贝
if (originValue instanceof Function) {
return originValue;
}
// 如果是对象类型,才需要创建新对象
if (map.has(originValue)) {
return map.get(originValue);
}
const newObj = Array.isArray(originValue) ? [] : {};
map.set(originValue, newObj);
// 遍历普通的key
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key], map);
}
// 单独处理Symbol类型的key
const symbolKeys = Object.getOwnPropertySymbols(originValue);
for (const symbolKey of symbolKeys) {
newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map);
}
return newObj;
}
// 测试
const set = new Set(["abc", "cba", "nba"]);
const s1 = Symbol("s1");
const info = {
name: "why",
age: 18,
friend: {
name: "kove",
address: {
name: "洛杉矶",
detail: "斯坦普斯中心"
}
},
// 特殊类型:Set
set: set,
// 特殊类型:function
running: function () {
console.log("running");
},
// 值的特殊类型: Symbol
symbolKey: Symbol("abc"),
// key是symbol时
[s1]: "aaaa"
};
info.self = info;
const newObj = deepCopy(info);
console.log(newObj);
console.log(newObj.self === newObj);
三、浅拷贝与深拷贝的应用场景
1.浅拷贝
-
对象的属性简单,没有嵌套对象: 当对象的属性都是基本数据类型(如数字、字符串、布尔值等)或者只有一层嵌套时,可以使用浅拷贝。
-
需要快速创建对象副本: 浅拷贝比深拷贝更高效,适用于需要快速创建对象副本且不需要考虑对象结构深度的情况。
-
只需要复制对象的一部分属性: 如果只需要复制对象的部分属性,可以使用浅拷贝来复制所需的属性,而不必复制整个对象。
-
对象中包含循环引用: 对于包含循环引用的对象,深拷贝会进入无限递归,而浅拷贝可以避免这个问题。
2.深拷贝
-
对象包含嵌套的子对象: 当对象中包含嵌套的子对象,且需要确保复制后的对象完全独立于原始对象时,应使用深拷贝。
-
修改复制对象不应该影响原始对象: 如果修改复制对象中的属性不应该影响原始对象,例如在进行数据修改时不想影响原始数据,应使用深拷贝。
-
对象中包含函数或正则表达式等特殊类型: 深拷贝可以复制对象中的所有属性,包括函数、正则表达式等特殊类型,而浅拷贝无法复制这些特殊类型。
-
需要递归地复制对象的所有子对象: 如果需要复制对象的所有子对象,而不仅仅是第一层的属性,应使用深拷贝。
四、手写实现封装深拷贝函数
function deepCopy(obj, visited = new Map()) {
// 如果是基本数据类型或 null,则直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (visited.has(obj)) {
return visited.get(obj);
}
// 处理特殊对象类型
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理 Set 类型
if (obj instanceof Set) {
const copy = new Set();
visited.set(obj, copy);
obj.forEach(value => {
copy.add(deepCopy(value, visited));
});
return copy;
}
// 处理 Map 类型
if (obj instanceof Map) {
const copy = new Map();
visited.set(obj, copy);
obj.forEach((value, key) => {
copy.set(key, deepCopy(value, visited));
});
return copy;
}
// 创建一个新的对象或数组来存储复制后的值
const copy = Array.isArray(obj) ? [] : {};
// 记录当前对象,防止循环引用
visited.set(obj, copy);
// 遍历原始对象的所有属性,并递归复制每个属性值
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
copy[key] = deepCopy(obj[key], visited);
}
}
return copy;
}