JavaScript - 深浅拷贝

#前言

JS 中的数据类型可分为两种:

  • 基本类型undefined , null , Boolean , String , Number , Symbol, BigInt
  • 引用类型Object , Array , Date , Function , RegExp

依据MDN文档,JavaScript的数据类型分为 原始值对象 ,理解它们的存储和复制方式对于实现深浅拷贝至关重要。

不同类型的存储方式:

  • 基本类型 :基本类型值在内存中占据固定大小,保存在 栈内存
  • 引用类型 :引用类型的值是对象,保存在 堆内存 中,而 栈内存 中存储的是对象的 变量标识符 以及对象在 堆内存 中的存储地址

不同类型的复制方式:

  • 基本类型 :从一个变量向另外一个新变量复制基本类型的值,会创建这个值的一个副本,并将该副本复制给新变量
  • 引用类型 :从一个变量向另一个新变量复制引用类型的值,其实复制的是储存地址,最终两个变量最终都指向同一个对象

#深浅拷贝的区别

  • 浅拷贝 :仅仅是复制了引用,彼此之间的操作会互相影响
  • 深拷贝 :在堆中重新分配内存,不同的地址,相同的值,互不影响

#浅拷贝

依据MDN文档,对象的 浅拷贝 是属性与拷贝的源对象属性共享相同的引用(指向相同的底层值)的副本。因此,当你更改源对象或副本时,也可能导致另一个对象发生更改。与之相比,在 深拷贝 中,源对象和副本是完全独立的。

#循环赋值

循环赋值
JavaScript
function shallowClone(object) {
// 只拷贝对象
if (!object) return object
// 依据 object 的类型判断是新建一个数组还是对象
const newObject = Array.isArray(object) ? [] : {}
// 遍历 object,并且判断是 object 的属性才拷贝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key]
}
}
return newObject
}
const weatherData = {
condition: 'sunny',
details: {
temperature: 25,
},
}
const newWeatherData = shallowClone(weatherData)
newWeatherData.condition = 'CLOUDY'
newWeatherData.details.temperature = 18
console.log(weatherData.condition) // => sunny
console.log(weatherData.details.temperature) // => 18

#Object.assign

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象,它将返回目标对象。

Object.assign
JavaScript
const weatherData = {
condition: 'sunny',
details: {
temperature: 25,
},
}
const newWeatherData = Object.assign({}, weatherData)
newWeatherData.condition = 'CLOUDY'
newWeatherData.details.temperature = 18
console.log(weatherData.condition) // => sunny
console.log(weatherData.details.temperature) // => 18

#扩展运算符(...)

扩展运算符(...)
JavaScript
const weatherData = {
condition: 'sunny',
details: {
temperature: 25,
},
}
const newWeatherData = { ...weatherData }
newWeatherData.condition = 'CLOUDY'
newWeatherData.details.temperature = 18
console.log(weatherData.condition) // => sunny
console.log(weatherData.details.temperature) // => 18

#深拷贝

在 JavaScript 中, 深克隆 是指对一个对象或数组进行完整的拷贝,包括嵌套的对象和数组。深克隆的实现有多种方式,具体选择取决于数据结构的复杂程度和性能要求。

#JSON.parse(JSON.stringify(...))

这是一种简单的 深拷贝 方法,适用于不包含特殊对象的情况。

JSON.parse(JSON.stringify(object))
JavaScript
const weatherData = {
condition: 'sunny',
details: {
temperature: 25,
},
}
const newWeatherData = JSON.parse(JSON.stringify(weatherData))
newWeatherData.condition = 'CLOUDY'
newWeatherData.details.temperature = 18
console.log(weatherData.condition) // => sunny
console.log(weatherData.details.temperature) // => 25

局限性:

会忽略undefined 会忽略symbol 不能序列化函数 不能解决循环引用的对象

#递归

递归 方法可以处理更复杂的对象结构,但需要考虑性能和循环引用问题。

递归
JavaScript
function deepClone(obj) {
if (obj === null) return obj
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
if (typeof obj !== 'object') return obj
const cloneObj = new obj.constructor()
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key])
}
}
return cloneObj
}
const weatherData = {
condition: 'sunny',
details: {
temperature: 25,
},
}
const newWeatherData = deepClone(weatherData)
newWeatherData.condition = 'CLOUDY'
newWeatherData.details.temperature = 18
console.log(weatherData.condition) // => sunny
console.log(weatherData.details.temperature) // => 25

局限性:

有爆栈的风险 大对象性能问题 循环引用问题

#structuredClone

全局的 structuredClone() 方法使用 结构化克隆算法 将给定的值进行 深拷贝 。这是现代浏览器提供的原生深拷贝方法,具有良好的性能和更广泛的兼容性。

structuredClone
JavaScript
const weatherData = {
condition: 'sunny',
details: {
temperature: 25,
},
}
const newWeatherData = structuredClone(weatherData)
newWeatherData.condition = 'CLOUDY'
newWeatherData.details.temperature = 18
console.log(weatherData.condition) // => sunny
console.log(weatherData.details.temperature) // => 25

优点:

性能比递归实现更好 可以处理循环引用 支持更多的内置类型

局限性:

不能克隆函数、DOM节点和部分内置对象 兼容性仍在改善中,较新的 API 可能不支持