在 JavaScript 开发中,对象的拷贝是一个常见但又容易出错的话题。今天我们来深入探讨深浅拷贝的区别、实现方式以及使用场景。
什么是拷贝?
简单来说,拷贝就是基于原对象,创建一个新对象。但这里有个关键点:拷贝操作只针对引用类型(对象、数组、函数等),因为基本类型(字符串、数字、布尔等)的赋值本身就是值的复制。
浅拷贝:表面功夫
浅拷贝创建了一个新对象,但只复制了原对象的第一层属性。如果属性值是对象,那么拷贝的只是该对象的引用地址。
浅拷贝的特点:
- 新对象的第一层属性修改不会影响原对象
- 但嵌套的对象属性修改会相互影响(因为共享同一引用)
常见的浅拷贝方法
1. [].slice(0)
- 数组里面的slice方法,接收两个参数第一个是起始下标,第二个是结束下标。只写起始下标的话就是全切一遍,并且不影响原数组得到一个新数组。
const arr = ['a', 'b', 'c', 'd', {age: 18}]
const arr2 = arr.slice(0)
arr[4].age = 20
console.log(arr2);
2. [...a]
- 数组里面的解构方法
const a = [1, 2, {age: 18}]
// const b = [3, 4]
let c = [...a]
a[2].age = 19
console.log(c);
3. [].concat()
- 数组的contact方法
const a = [1, 2, 3, {age: 18}]
const d = [].concat(a)
a[3].age = 20
4. arr.toReversed().reverse()
- toReversed + reverse(较新的方法)
const arr = [1, 2, 3]
const arr2 = arr.toReversed().reverse()
console.log(arr2);
5. Object.assign({},obj)
- Object.assign 方法
const obj={
name:'汉堡'
}
const obj2={
age:18
}
Object.assign(obj,obj2)
console.log(obj);
问题展示
这里浅拷贝再改变obj的值就会使得新拷贝的对象里面的值受到影响,所以这是拷贝了第一层,并没有拷贝到里面对象的引用地址。
深拷贝:彻底分离
深拷贝会递归地拷贝所有层级的属性,创建一个完全独立的新对象,新旧对象之间互不影响。
常见的深拷贝方法
structuredClone -- 无法拷贝函数
const obj = {
name: '汉堡',
age: 18,
like: {
n: '洗脚',
m: '台球'
},
a: 123n,
}
const newObj = structuredClone(obj)
obj.like.m = '蓝球'
console.log(newObj);
JSON.parse(JSON.stringify(obj))
无法处理 bigint,undefined,NaN,Infinity,function
const obj = {
name: '俊杰',
age: 18,
like: {
n: '洗脚',
m: '台球'
},
say() {
console.log('hello');
},
a: undefined,
b: null,
c: NaN,
d: Infinity
}
const oo = JSON.parse(JSON.stringify(obj))
obj.like.m = '篮球'
console.log(oo);
手写深拷贝函数
对于更复杂的需求,我们可以自己实现一个深拷贝函数: 这里利用递归这种方法来层层拷贝要拷贝的对象
const obj = {
name: '俊杰',
age: 18,
like: {
n: '洗脚',
m: '台球'
}
}
function deepClone(obj) {
let o = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof(obj[key]) == 'object' && obj[key] !== null) {
const childObj = deepClone(obj[key])
o[key] = childObj
} else {
o[key] = obj[key]
}
}
}
return o
}
const newObj = deepClone(obj)
obj.like.m = '篮球'
console.log(newObj);
总结
适合浅拷贝的场景:
- 对象结构简单,没有嵌套对象
- 性能要求高(深拷贝更耗资源)
- 明确知道不会修改嵌套属性
适合深拷贝的场景:
- 状态管理
- 表单数据的初始化和重置
- 需要完全独立的对象副本
- 处理配置对象等需要隔离的数据