在我们日常代码编写中,经常会遇到需要复制一个数组或者对象的时候。如果复制对象为原始数据类型,那很简单。但如果复制的对象里包含引用类型时,原对象的引用数据修改时,新复制的对象是否会修改呢?这就是我们今天要探讨的问题——浅拷贝与深拷贝。
我们先通过一些简单的例子来看看它们到底是什么:
let a = 1
let b = a
a = 2
console.log(b);
let obj = {
c: 3
}
let newObj = obj
obj.c = 4
console.log(newObj);
根据这个简单的例子,我们可以知道:
新拷贝的对象不会因为原对象属性的修改而修改的拷贝是深拷贝;反之,新对象会受原对象影响的为浅拷贝(只拷贝了对象的第一层,而里面的子对象拷贝的还是引用地址)。
原始类型一定为深拷贝,没有讨论的意义。我们讨论的浅拷贝与深拷贝都是基于引用类型的拷贝。
浅拷贝
让我们一起看看JS中浅拷贝有那些实现方法
1.Object.create(obj)
Object.create()函数会创建一个新对象,它是一个空对象,因为它没有直接定义任何属性。它的属性是通过原型链继承自 obj。
let obj = {
a: 1,
b: 2
}
let newObj = Object.create(obj)
obj.a = 3
console.log(newObj.a);
console.log(newObj);
这个例子中,newObject的a属性会随着被拷贝对象obj的修改而修改,但是newObject并没有复制obj中的属性,而是与obj建立原型链关系。所以Object.create(obj)严格意义上来说并不能算是浅拷贝,只是效果与浅拷贝类似,这里需要注意。
2.Object.assign({},obj2)
const user = {
name: 'Jack',
like: {
n: '泡脚',
m: '吃鸡'
}
}
let newObj = Object.assign({},user)
console.log(newObj);
这就是
Object.assign({},...obj)的一个用法,那我们来验证一下,Object.assign({},...obj)是否是浅拷贝呢?
const user = {
name: 'Jack',
like: {
n: '泡脚',
m: '吃鸡'
}
}
let newObj = Object.assign({},user)
console.log(newObj);
user.like.n = '喝茶'
console.log(newObj);
当obj里的引用对象like里的属性发生改变时,拷贝的对象newObject里的引用对象里的值也跟着改变了,意味着它就是浅拷贝。
3.[].concat(arr)
let arr = [{a:1},2]
let newArr = [].concat(arr)
arr[0].a = 10
console.log(newArr);
同样,原对象的引用类型发生改变时,新拷贝的对象同样发生改变,因此它也是浅拷贝。
4.数组解构
let arr = [1, 2, {a: 3}]
let newArr = [...arr]
arr[0] = 4
arr[2].a = 5
console.log(newArr);
同样,数组解构也是一种浅拷贝。
5.arr.slice()
const arr = [1, {a: 2}, 3, 4, 5];
const newArr1 = arr.slice(1, 3); // 左闭右开
arr[1].a = 6
console.log(newArr1);
6.arr.toReversed().reverse()
arr.toReversed()是2022年才引入的新方法,此方法用于返回一个新数组,内容是原数组元素的反向排列。arr.reverse()用于反转一个数组中的元素。要注意的是,该方法会直接修改原数组,而不是创建一个新数组。 那我们先对一个数组进行.toReversed()操作产生一个新的反转过的数组,再用.reverse()就可以拷贝出一个数组了,那我们来验证一下,这是不是浅拷贝呢?
let arr = [1, {a: 2}, 3, 4, 5]
let newArr = arr.toReversed().reverse()
console.log(newArr);
arr[1].a = 6
console.log(newArr);
显而易见,它同样也是浅拷贝。
那么,关于浅拷贝我们就这六种方法,接下来我们来看看深拷贝是怎么实现的。
深拷贝
1.JSON.parse(JSON.stringify(obj))
定义
JSON.stringify(obj): 这个方法将一个 JavaScript 对象转换为一个 JSON 字符串。它会将对象的所有可枚举属性序列化为字符串,包括嵌套对象,但不包括函数、undefined、Symbol、不属于对象的属性(如原型链),以及Map和Set等特殊对象。JSON.parse(jsonString): 这个方法将一个 JSON 字符串解析为一个 JavaScript 对象。它会创建一个新对象,并努力复制字符串中的值。
用法示例
下面是一个简单的示例,演示如何使用 JSON.parse(JSON.stringify(obj)) 来进行深拷贝:
const original = {
name: 'Alice',
age: 30,
hobbies: ['reading', 'traveling'],
address: {
city: 'New York',
zip: '10001'
}
};
// 使用 JSON.parse 和 JSON.stringify 进行深拷贝
const copy = JSON.parse(JSON.stringify(original));
// 修改拷贝的对象
copy.name = 'Bob';
copy.hobbies.push('cooking');
copy.address.city = 'Los Angeles';
console.log(original.name); // 输出: 'Alice' (原对象未改变)
console.log(original.hobbies); // 输出: ['reading', 'traveling'] (原对象未改变)
console.log(original.address.city); // 输出: 'New York' (原对象未改变)
console.log(copy); // 输出: { name: 'Bob', age: 30, hobbies: ['reading', 'traveling', 'cooking'], address: { city: 'Los Angeles', zip: '10001' } }
原对象的引用类型发生改变也不会改变拷贝对象的引用类型数据,所以JSON.stringify(obj)为深拷贝。
但是使用JSON.stringify(obj)时需要注意一些地方:
- 不能识别 bigint
- 不能拷贝 undefined Symbol function NaN Infinity
- 无法处理循环引用
structuredClone()
由于一直以来js都没有一个完美的深拷贝方法,一直被世界各地程序员诟病,终于,官方顶不住各方压力,再前几年创建了一个直接可以进行深拷贝的方法——structuredClone()
让我们看看用法:
const user = {
name: 'Jack',
like: {
n: '泡脚',
m: '吃鸡'
}
}
let newUser = structuredClone(user);
user.like.n = '骑车';
console.log(newUser);
总结
#拷贝 -只针对引用类型 -基于原对象,拷贝得到一个新对象
1.浅拷贝:新对象会受原对象影响(只拷贝了对象的第一层,而里面的子对象拷贝的还是引用地址)
-
Object.create(x)
-
Object.assign({},obj2)
-
[].concat(arr)
-
数组解构
-
arr.slice()
-
arr.toReversed().reverse()
-
实现原理:
2.深拷贝:新对象不受原对象影响
-
JSON.parse(JSON.stringify(obj)) 1.不能识别 bigint 2.不能拷贝 undefined Symbol function NaN Infinity 3.无法处理循环引用
-
structuredClone()
看到这里,相信大家对浅拷贝与深拷贝有了更深的理解。