干货分享——JS深浅拷贝
引言
在JS开发过程中,对象的拷贝是一个频繁出现的任务。根据拷贝的深度,我们可以分为浅拷贝和深拷贝两种类型。这两种拷贝方式在处理对象属性时有着本质的区别,尤其是当属性值为引用类型时。接下来,让我们详细探讨一下这两种拷贝方式。
浅拷贝
浅拷贝仅仅复制对象的顶层属性。如果属性是基础类型,直接复制其值;如果属性是引用类型,则复制引用地址,而不是实际的对象内容。这意味着,原对象中的引用类型属性与新对象中的对应属性实际上指向的是同一个内存地址
。
因此,原对象中的引用类型属性的修改会影响到新对象。
常见浅拷贝方法
1. Object.assign({}, b)
let a = {
myname: 'xx',
like: {
sport: 'running'
}
}
let b = Object.assign({}, a);
a.myname = 'yy';
a.like.sport = 'swimming';
console.log(a);
console.log(b);
可见,原对象原始类型的更改不会影响新对象,引用类型的更改会影响新对象。
2. 扩展运算符
let a = {
myname: 'xx',
like: {
sport: 'running'
}
}
let b = { ...a } // 扩展运算符
a.myname = 'yy';
a.like.sport = 'swimming';
console.log(a);
console.log(b);
3. [].concat(arr)
let arr = [1, 2, 3, { a: 10 }];
let newArr = [].concat(arr)
console.log(newArr);
4. arr.slice(0)
let arr = [1, 2, 3, { a: 10 }];
let newArr = arr.slice(0)
console.log(newArr);
5. arr.toReversed().reverse()
let arr = [1, 2, 3, { a: 10 }];
let newArr = arr.toReversed().reverse()
console.log(newArr);
浅拷贝原理
- 借助for in遍历原对象,将原对象的属性增加在新对象中
- 因为for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key) 来判断要拷贝的属性是不是显式具有的属性
手写浅拷贝函数
let obj = {
myname: 'ww',
like: {
a: 'food'
}
}
// 浅拷贝函数
function shallow(obj) {
let newObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
console.log(shallow(obj));
深拷贝
相比之下,深拷贝则是完全复制一个对象,包括对象的所有层级属性。即使属性是引用类型,深拷贝也会递归地复制其所有子对象,确保原对象与新对象完全独立,互不影响。也就是说:
原对象中引用类型属性的修改不会影响新对象。
常见深拷贝方法
1. JSON.parse(JSON.stringify(obj))
实现深拷贝的一种简便方法。它先将对象转换为JSON字符串,然后再将字符串解析回对象,从而创建一个全新的对象副本。
let obj = {
name: 'ww',
age: 18,
like: {
sport: 'swimming'
},
a: true,
b: undefined,
c: null,
d: Symbol(1),
e: function () { }
}
let obj2 = JSON.parse(JSON.stringify(obj))
obj.like.n = 'running'
console.log(obj2);
缺点:
-
不能拷贝 undefined、Symbol、function 类型的值
相信从上面的拷贝结果也能看出这一点。
-
不能识别 BigInt 类型
如果你试图拷贝BigInt 类型的变量,就会产生以下报错:
TypeError:不知道如何序列化 BigInt
-
不能处理循环引用
以上方代码为例,如果我们在对象后面加上以下代码:
obj.c = obj.like
obj.like.m = obj.c
它就会产生循环引用,这个时候拷贝obj,就会产生以下报错:
TypeError:将循环结构转换为 JSON
2. structuredClone(obj)
这是一个新的全局函数,用于创建对象的结构化克隆。它可以正确处理循环引用,并支持更多的数据类型。但请注意,它目前还不是所有环境都支持
。
const user = {
name: {
firstName: 'w',
lastName: 'w'
},
age: 18
}
let newObject = structuredClone(user);
user.name.firstName = 'y';
console.log(newObject, user);
深拷贝原理
- 借助for in遍历原对象,将原对象的属性增加在新对象中
- 因为for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key) 来判断要拷贝的属性是不是显式具有的属性
- 如果遍历到的属性值是原始值类型,直接往新对象中赋值
- 如果是引用类型,递归创建新的子对象
手写深拷贝函数(丐中丐版)
const user = {
name: {
firstName: 'w',
lastName: 'w'
},
age: 18
}
function deep(obj) {
let newObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] instanceof Object) {
newObj[key] = deep(obj[key]);
} else {
newObj[key] = obj[key];
}
}
}
return newObj;
}
let newUser = deep(user);
user.name.firstName = 'y'; //测试是否为深拷贝
console.log(user);
console.log(newUser);
虽然它只能完成对象多层嵌套的深拷贝,但是对于面试来说足够了。
结语
在JavaScript开发中,对对象的拷贝操作是非常常见的需求。通过掌握浅拷贝和深拷贝的原理和方法,我们可以更加灵活地处理对象的复制需求,避免因为引用类型属性而带来的意外问题。希望本文对你有所帮助!
转载自:https://juejin.cn/post/7371655419360608308