对象的深拷贝与浅拷贝
什么是浅拷贝
只拷贝了数据对象的第一层,深层次的数据值与原始数据会互相影响(拷贝后的数据与原始数据还存有关联)
常见浅拷贝的方式:Object.assign()、扩展运算符
1 2 3 4 5 6 7 8 9
| const obj1 = { name: 'dog', info: { age: 3 } } const obj2 = Object.assign({}, obj1)
const obj2 = { ...obj1 }
obj2.name = 'cat' obj2.info.age = 4 console.log(obj1) console.log(obj2)
|
什么是深拷贝
不管数据对象有多少层,改变拷贝后的值都不会影响原始数据的值。(拷贝后的数据与原始数据毫无关系)
常见深拷贝的方式:JSON.parse() 和 JSON.stringify() 配合使用
1 2 3 4 5 6
| const obj1 = { name: 'dog', info: { age: 3 }, fn: function () {} } const obj2 = JSON.parse(JSON.stringify(obj1)) obj2.name = 'cat' obj2.info.age = 4 console.log(obj1) console.log(obj2)
|
浅拷贝可以使用 Object.assign 或者遍历赋值的方式手动实现。
深拷贝可以通过JSON.stringify() 与 JSON.parse()实现,但对于对象有要求,因为在遇到函数,undefined,Sybmol,Date对象时会自动忽略,遇到正则时会返回空对象。也可以通过递归的方式手动实现深拷贝。
浅拷贝的实现
Object.assign()
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
1 2 3
| function clone(obj) { return Object.assign({}, obj) }
|
遍历赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function clone(obj) { let cloneObj = Array.isArray(obj) ? [] : {}; for (let key of Object.keys(obj)) { cloneObj[key] = obj[key] } return cloneObj }
let c1 = { a: 1, b: 2, c: { d: 4, e: 5 } }
let d1 = clone(c1) console.log(c1, d1)
c1.c.d = 12 console.log(c1, d1)
|
JSON.stringify() 与 JSON.parse()
1 2 3
| function deepClone(obj) { return JSON.parse(JSON.stringify(obj)) }
|
问题:
- 对象中的时间类型会被变成字符串类型数据
- 对象中的 undefined 和 函数类型会直接丢失
- 对象中的 NaN、Infinity、-Infinity 会变成 null
- 对象循环引用时会报错
这些问题大部分源于 JSON.stringify(),毕竟这个函数的初衷不是用来做拷贝的。当然,json.stringify 提供了第二个参数(为一个函数),对象中的每个值会递归交给函数处理,可以在该函数中判断类型做相应处理。
递归
简单版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 (const key of Object.keys(obj)) { cloneObj[key] = deepClone(obj[key]) } return cloneObj }
|
稍微复杂版
- 解决 symbol 无法作为键的问题
Reflect.ownKeys 方法返回一个由目标对象自身的属性键组成的数组 等于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
- 解决循环引用问题
1 2 3 4 5 6
| const obj = { a: 1 } obj.b = obj
const newObj = deepClone(obj)
|
报错:栈内存溢出,死循环了。
因为在递归遍历obj的属性时,obj有属性指向自身,因此会无限循环。
开辟新内存记录出现过的 obj,如果已经出现过就不再遍历,直接返回。
- 解决垃圾回收问题
使用 WeakMap,WeakMap是弱引用,不影响垃圾回收(WeakMap键指向的对象的其它引用被清除后,该对象会被垃圾回收)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function deepClone(obj, hash = new WeakMap()) { 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
if (hash.has(obj)) return hash.get(obj)
const cloneObj = new obj.constructor()
hash.set(obj, cloneObj)
Reflect.ownKeys(obj).forEach(key => { cloneObj[key] = deepClone(obj[key], hash) }) return cloneObj }
|
日常开发
日常开发中,如果要使用深拷贝,为了兼容各种边界情况,一般是使用三方库如 lodash
1 2 3
| npm i --save lodash import _ from 'lodash' const cloneObj = _.cloneDeep(obj)
|
一个新的深拷贝API,ES 的一部分,目前兼容性还不够好。chrome >= 98才支持。
1
| structuredClone(value: any): any
|
这个函数有第二个参数 transferables,这个参数很少有用。详细信息,请参考MDN 页面structuredClone()
缺陷:
- 一些内置对象不能被复制,structuredClone()会抛出DOMException
Functions (ordinary functions, arrow functions, classes, methods)
DOM 节点DOM nodes
- structuredClone()不会复制对象的原型链
如果structuredClone()与类实例一起使用,将获得一个普通对象作为返回值,因为结构化克隆会丢弃对象的原型链。
- structuredClone()并不能复制DOM节点特性属性:
访问器 get 变成了数据属性。
在副本中,特性属性始终具有默认值。
1 2 3
| writable: true, enumerable: true, configurable: true,
|