JavaScript - 深浅拷贝
前言
JS 中的数据类型可分为两种:
- 基本类型:
undefined
,null
,Boolean
,String
,Number
,Symbol
- 引用类型:
Object
,Array
,Date
,Function
,RegExp
等
不同类型的存储方式:
- 基本类型:基本类型值在内存中占据固定大小,保存在
栈内存
中 - 引用类型:引用类型的值是对象,保存在
堆内存
中,而栈内存
中存储的是对象的变量标识符
以及对象在堆内存
中的存储地址
不同类型的复制方式:
- 基本类型:从一个变量向另外一个新变量复制基本类型的值,会创建这个值的一个副本,并将该副本复制给新变量
- 引用类型:从一个变量向另一个新变量复制引用类型的值,其实复制的是储存地址,最终两个变量最终都指向同一个对象
深浅拷贝的区别
- 浅拷贝:仅仅是复制了引用,彼此之间的操作会互相影响
- 深拷贝:在堆中重新分配内存,不同的地址,相同的值,互不影响
浅拷贝
对象的浅拷贝是属性与拷贝的源对象属性共享相同的引用(指向相同的底层值)的副本。因此,当你更改源对象或副本时,也可能导致另一个对象发生更改。与之相比,在深拷贝中,源对象和副本是完全独立的。
赋值
循环赋值javascript
123456789101112131415161718192021222324252627282930function 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 obj = {a: 'hello',b: {c: 'js',},}const newObj = shallowClone(obj)newObj.a = 'HELLO'newObj.b.c = 'JS'console.log(obj.a) // => helloconsole.log(obj.b.c) // => JS
Object.assign
Object.assignjavascript
12345678910111213const obj = {a: 'hello',b: {c: 'js',},}const newObj = Object.assign({}, obj)newObj.a = 'HELLO'newObj.b.c = 'JS'console.log(obj.a) // => helloconsole.log(obj.b.c) // => JS
扩展运算符(...)
扩展运算符(...)javascript
12345678910111213const obj = {a: 'hello',b: {c: 'js',},}const newObj = { ...obj }newObj.a = 'HELLO'newObj.b.c = 'JS'console.log(obj.a) // => helloconsole.log(obj.b.c) // => JS
深拷贝
在 JavaScript 中,深克隆是指对一个对象或数组进行完整的拷贝,包括嵌套的对象和数组。深克隆的实现有多种方式,具体选择取决于数据结构的复杂程度和性能要求。
JSON.parse(JSON.stringify(...))
JSON.parse(JSON.stringify(object))javascript
12345678910111213const obj = {a: 'hello',b: {c: 'js',},}const newObj = JSON.parse(JSON.stringify(obj))newObj.a = 'HELLO'newObj.b.c = 'JS'console.log(obj.a) // => helloconsole.log(obj.b.c) // => js
局限性:
会忽略 undefined 会忽略 symbol 不能序列化函数 不能解决循环引用的对象递归
递归javascript
12345678910111213141516171819202122232425262728function deepClone(obj) {if (obj === null) return objif (obj instanceof Date) return new Date(obj)if (obj instanceof RegExp) return new RegExp(obj)if (typeof obj !== 'object') return objconst cloneObj = new obj.constructor()for (let key in obj) {if (obj.hasOwnProperty(key)) {cloneObj[key] = deepClone(obj[key])}}return cloneObj}const obj = {a: 'hello',b: {c: 'js',},}const newObj = deepClone(obj)newObj.a = 'HELLO'newObj.b.c = 'JS'console.log(obj.a) // => helloconsole.log(obj.b.c) // => js
局限性:
有爆栈的风险 大对象性能问题 循环引用structuredClone
全局的 structuredClone() 方法使用结构化克隆算法将给定的值进行深拷贝。
structuredClonejavascript
12345678910111213const obj = {a: 'hello',b: {c: 'js',},}const newObj = structuredClone(obj)newObj.a = 'HELLO'newObj.b.c = 'JS'console.log(obj.a) // => helloconsole.log(obj.b.c) // => js