今天来学习一下JS中的拷贝:
let obj = {
name: 'Ro9nin'
}
let obj2 = obj
obj.name = 'Ro9n1n'
console.log(obj2.name);
上面的let obj2 = obj
这一操作不能叫拷贝。因为这个操作只是让obj2
只是获得了 obj
的引用地址,这意味着 obj2
和 obj
指向内存中的同一个对象,而obj2
并未创建一个和obj
一样的新对象;当对象obj
身上的name
改变时,因为二者的引用地址一样,obj2.name
也会跟着变化。
所以,在JavaScript中,根据拷贝的层次,可以分为两种类型:浅拷贝和深拷贝。接下来我们就来细聊它们的区别以及实现的方式:
浅拷贝
浅拷贝
创建一个新对象,并将原始对象中的所有非引用类型属性值复制到新对象中。对于引用类型属性,它只复制了指向原始对象的引用,而不是复制引用所指向的实际数据。这意味着如果原始对象和浅拷贝的对象都引用了同一个数组或对象,那么对其中一个对象的修改会影响到另一个对象。所以浅拷贝后的新对象会受到原对象的影响而改变;
js中有哪些方法可以实现浅拷贝呢?
-
- Object.create(obj)
在js中,Object.create()
用于创建一个新对象,同时指定新创建的对象的原型.
let obj = {
a : 2
}
let newObj = Object.create(obj)
console.log(newObj);
console.log(newObj.a);
这个例子中,使用Object.create(obj)
创建了一个新对象newObj
,并把它的原型(prototype)设置为obj
;这意味着新创建的对象newObj
会继承obj
的中的a
,使用newObj.a
同样能访问到其原型上的a
的值。
但使用 Object.create(obj)
的方式不算真正意义上的拷贝 ,这个方法主要是用来实现原型继承,使得新对象可以访问到obj
上的属性和方法。
-
- [].concat(arr)
[].concat(arr)
是js中用于将数组 arr
连接到一个新数组中的方法。它会返回一个新数组,其中包含原始数组和 arr
中的所有元素。
let arr = [1,2,3,{a:1}]
let arr2 = [].concat(arr)
arr[3].a = 2
console.log(arr2);
输出结果为 [1,2,3,{a:2}]
-
- Object.assign({},obj)
在JavaScript中,assign()
方法是 Object
构造函数的一个静态方法,用于将一个或多个源对象的可枚举属性的值复制到目标对象中。此方法会在目标对象上进行就地修改,并返回修改后的目标对象。
let obj = {
a: 1,
b: [1,2]
}
let obj2 = Object.assign({},obj)
obj.b.push(3)
console.log(obj2.b);
输出结果为[1,2,3]
利用这一特性,我们可以通过Object.assign({},obj)
来完成浅拷贝的操作;将目标对象里的属性都复制到一个空对象中。这些属性在新对象中仍然是通过引用共享的,且会被原对象影响。
-
- arr.slice(0)
在js中,数组身上的slice
方法,是用于从数组中提取一个新的子数组的方法。
let arr = [1,2,3,{a:1}]
let arr2 = arr.slice(0)
arr[3].a = 2
console.log(arr2);
输出结果为 [1,2,3,{a:2}]
-
- 数组解构 ...
在数组中,也可以通过解构的方法,实现浅拷贝;在js中解构数组用...
表示。
let arr = [1,2,3,{a:1}]
let arr2 = [...arr] // 将原数组元素解构到新的空数组当中
arr[3].a = 2
console.log(arr2);
输出结果为 [1,2,3,{a:2}]
- 手写方法实现浅拷贝
浅拷贝的原理,即是创建一个副本对象,并把原对象的原始类型值复制过来,而引用类型则是复制其引用地址。在知道原理后,手动写一个实现浅拷贝的方法也很简单:
let obj = {
a: 1,
b: {n: 1}
}
function clone(obj) {
let newobj = {}
for (let key in obj){
if(obj.hasOwnProperty(key)){
newobj[key] = obj[key]
}
}
return newobj
}
let obj2 = clone(obj)
obj.b.n=2
console.log(obj2);
在这个示例中,clone
函数实现的是一个浅拷贝。虽然newobj
包含了obj
的属性a
和b
,但是当b
是一个对象时,newobj.b
只是指向了obj.b
的同一引用,而不是创建了一个新的独立对象。
因此,当执行obj.b.n = 2
时,实际上改变了obj
和newobj
共享的b
对象的属性n
。结果是,当打印obj2
时,将看到b
对象的n
属性已经被更新为2
。
深拷贝
深拷贝不仅复制对象本身,还递归地复制对象内的所有引用类型。这意味着新对象及其内部的引用类型数据都是完全独立的,修改其中任何一个都不会影响到其他对象。
在js中有以下方法可以实现深拷贝:
-
- JSON.parse(JSON.stringify(obj))
在js中,JSON.parse(JSON.stringify(obj))
是一种常用的深拷贝对象的方法。
let obj = {
a: 1,
b: {c: 2 }
};
let newObj = JSON.parse(JSON.stringify(obj));
newObj.a = 3;
newObj.b.c = 4;
console.log(obj.a);
console.log(obj.b.c);
结果为 1 , 2
可以看到,当修改 newObj
的属性时,原始对象 obj
的属性并没有受到影响,这表明深拷贝成功实现了对象的完全独立复制。
-
- structuredClone()
structuredClone()
提供了一种创建对象、数组以及一些特殊类型值的深拷贝的方法。
let obj = {
a: 1,
b: {n: 2},
c: 'DAMN',
d: true,
e: undefined,
f: null,
// g: function(){},
// h: Symbol(1),
i: 123n
}
const newObj = structuredClone(obj)
obj.b.n = 20
console.log(newObj);
在上面的执行结果中,可以看到structuredClone()
方法可以实现深拷贝。
- 手写方法实现深拷贝
Object.prototype.c = 3
let obj = {
a: 1,
b: {n: 1}
}
function deepClone(obj) {
let newObj = {}
for (let key in obj){
if(obj.hasOwnProperty(key)){
if(typeof obj[key] === 'object' && obj[key] !== null){
newObj[key] = deepClone(obj[key]);
} else {
newObj[key] = obj[key]
}
}
}
return newObj
}
let obj3 = deepClone(obj)
obj.b.n=2
console.log(obj,obj3);
deepClone
函数实现了一个基本的深拷贝,但是它只检查了obj
对象自身的属性,没有考虑原型链。这意味着newObj
将不会包含从Object.prototype
继承的c
属性。
关于obj.b.n = 2
之后的行为,deepClone
正确地创建了obj
的一个深拷贝,即obj3
。因此,修改obj.b.n
不会影响obj3.b.n
的值,因为b
对象也被深拷贝了。