浅拷贝,深拷贝,带你学得有滋有味!
前言
在JavaScript的世界里,数据的复制与传递是编程日常的核心之一。深入了解JS中的拷贝机制,不仅是编写高效、 bug-free代码的基础,更是通往高级编程技巧的必经之路。本篇将带你深入浅出,探索深拷贝与浅拷贝的奥秘。
1.浅拷贝
基于原对象,得到新对象,原对象内容的修改会改变新对象
1.1 Object.create()
let a = {
name: '艾总',
like: {
n: 'running'
}
}
let b = Object.create(a)
a.name = '黎总'
console.log(a);
console.log(b);
console.log(b.name);
//{ name: '黎总', like: { n: 'running' } }
//{}
//黎总
我们可以从这里面看出这个方法的作用是创建一个对象,并且让此对象隐式继承该对象的方法。由于修改了原对象中的属性之后新创建的对象属性出现更改,因此为浅拷贝。
1.2 Object.assign({}, a)
let a = {
name: '艾总',
like: {
n: 'running'
}
}
let c = {
age: 18
}
let d = Object.assign(a, c)
console.log(d);
console.log(a);
//{ name: '艾总', like: { n: 'running' }, age: 18 }
//{ name: '艾总', like: { n: 'running' }, age: 18 }
首先我们可以发现这个方法的作用是把后者对象的属性传给前者对象,并且再次返回对象。
let a = {
name: '艾总',
like: {
n: 'running'
}
}
let d = Object.assign({}, a)
a.like.n = 'swimming'
console.log(d);
//{ name: '艾总', like: { n: 'swimming' } }
我们可以看见,当改变了a,d也会随之改变,因此这个也为浅拷贝。
1.3 [ ].concat(arr)
接下来我们来聊一聊,数组里的浅拷贝。
let arr = [1, 2, 3, { a: 10 }]
let newArr = [1, 2].concat(arr)
arr[3].a = 100
console.log(arr);
console.log(newArr);
//[ 1, 2, 3, { a: 100 } ]
//[ 1, 2, 1, 2, 3, { a: 100 } ]
这个方法的作用是将两个数组实现拼接,我们依然发现是个浅拷贝。
1.4 数组解构
arr = [1, 2, 3, 4, 5, { age: 18 }]
const arr1 = [...arr]
arr[5].age = 20
console.log(arr);
console.log(arr1);
//[ 1, 2, 3, 4, 5, { age: 20 } ]
//[ 1, 2, 3, 4, 5, { age: 20 } ]
我们还可以通过数组解构的方式来实现浅拷贝。
1.5 arr.slice(0)
arr = [1, 2, 3, 4, 5, { age: 18 }]
const arr1 = arr.slice(0)
arr[5].age = 20
console.log(arr);
console.log(arr1);
//[ 1, 2, 3, 4, 5, { age: 20 } ]
//[ 1, 2, 3, 4, 5, { age: 20 } ]
我们通过slice方法来截取数组,依然浅拷贝。
1.6 arr.toReversed().reverse()
arr = [1, 2, 3, 4, 5, { age: 18 }]
const arr1 = arr.toReversed().reverse()
arr[5].age = 20
console.log(arr);
console.log(arr1);
//[ 1, 2, 3, 4, 5, { age: 20 } ]
//[ 1, 2, 3, 4, 5, { age: 20 } ]
我们先反转数组(会有返回值)然后再次反转,最终得到原数组。
1.7 手写一个浅拷贝
let obj = {
name: '艾总',
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));
//{ name: '艾总', like: { a: 'food' } }
- 实现原理:
- 借助for in 遍历原对象,将原对象的属性增加到新对象中
- 因为for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的对象是不是对象显式具有的
- 关于手写一个浅拷贝,我们可以先创建一个对象,然后往对象里传值,首先我们不必拷贝隐式属性,因此先做判断,然后赋值,最终返回这个对象,我们就实现了浅拷贝。
2 深拷贝
基于原对象,得到新对象,原对象内容的修改不会改变新对象
既然浅拷贝拷贝的还是原对象的地址,那么能不能彻底拷贝一个和原来内容一样,但是改变原内容,不改变新内容呢?
2.1 JSON.parse(JSON.stringify(obj))
let obj = {
name: '艾总',
age: 18,
like: {
n: 'Audirs7'
},
a: true,
b: undefined,
c: null,
d: Symbol(1),
f: function () { }
}
let obj2 = JSON.parse(JSON.stringify(obj))
obj.like.n = '66666'
console.log(obj2);
//{ name: '艾总', age: 18, like: { n: 'Audirs7' }, a: true, c: null }
我们可以发现obj改变的属性obj2并没有跟着改变,因此为深拷贝,但是这个方法存在一些问题,
- JSON.parse(JSON.stringify(obj))
- 不能识别bigint类型
- 不能拷贝undefined, symbol, function 类型的值
- 不能处理循环引用
但是排除这些情况,还是非常好用的。
2.2 structuredClone()
const user = {
name: {
firstName: '牛',
lastName: '蜗'
},
age: 18
}
const newUser = structuredClone(user)
user.name, firstName = '6666666666'
console.log(newUser);
//{ name: { firstName: '牛', lastName: '蜗' }, age: 18 }
js官方打造了非常好用的一个拷贝方法,这个可以实现彻底的深拷贝。
2.3 手写一个深拷贝(丐版)
- 实现原理:
- 借助for in 遍历原对象,将原对象的属性增加到新对象中
- 因为for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的对象是不是对象显式具有的
- 如果遍历到的属性值是原始值类型,直接往新对象中赋值,如果是引用类型则递归创建新的子对象。
const user = {
name: {
firstName: '牛',
lastName: '蜗'
},
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
}
const newUser = deep(user)
user.name.firstName = '666666666'
console.log(newUser);
//{ name: { firstName: '牛', lastName: '蜗' }, age: 18 }
我们可以发现,这个方法也成功实现了深拷贝,其他部分和浅拷贝一样,核心内容在于判断对象的某个属性是否为对象,如果为对象的话,则采用递归的方式。碰见函数,数组等也是一样的,因此我们就写了一个简易版的。
3. 小结
JavaScript中,浅拷贝只复制对象的第一层属性,内部对象仍共享引用,修改会相互影响;深拷贝则递归复制所有层级,确保源对象与拷贝对象完全独立。简而言之,浅拷贝浅尝辄止,深拷贝深入骨髓,适用于数据隔离与保护。
转载自:https://juejin.cn/post/7371716397296746505