工作中经常用到对象拷贝,但是具体实现原理并不是很懂。通过学习手撕
浅拷贝和深拷贝
浅拷贝
- 浅拷贝只拷贝对象最外一层,深层次的对象引用不考虑
// 被拷贝对象
const oldObj = {
a: 1,
b: {
c: 2,
d: [3, 4]
}
}
// 方法1,使用ES6扩展运算符
const obj1 = {...oldObj}
// 方法2,遍历循环
function myClone(obj) {
// 通过对象的构造函数new新实例
let newObj = new obj.constructor
for(let key in obj) {
if(!obj.hasOwnProperty(key)) return
newObj[key] = obj[key]
}
return newObj
}
const obj2 = myClone(oldObj)
深拷贝
- 深拷贝将深层次的对象也拷贝
// 被拷贝对象
const oldObj = {
a: 1,
b: {
c: 2,
d: [3, 4]
}
}
/*
方法1,使用JSON方法,深拷贝没有复杂数据对象
优点:快、便捷
缺点:1. 正则会转成空对象
2. 函数、undefined会被直接省略
3. 日期对象转换成固执值
*/
const obj1 = JSON.parse(JSON.stringify(oldObj))
/*
方法2,遍历循环
*/
function deepClone(obj) {
// 判断为null,直接返回null
if(obj === null) reutrn null
// 判断为非对象,直接返回原值
if(typeof obj !== 'object') return obj
// 判断为函数,返回新函数
if(typeof obj === 'function') return new Function('return' + obj.toString())()
// 判断为日期对象,返回新日期对象
if(obj instanceof Date) return new Date(obj)
// 判断为正则对象,返回新正则对象
if(obj instanceof RegExp) return new RegExp(obj)
// 通过对象的构造函数new新实例
let newObj = new obj.constructor
for(let key in obj) {
// 判断私有属性,递归深拷贝
if(obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key])
}
}
return newObj
}
const obj2 = myClone(oldObj)
深拷贝-循环引用问题
- 深拷贝旧对象属性指向旧对象,该如何优化深拷贝?
const oldObj = {
a: 1,
b: {
c: 2,
d: [3, 4]
}
}
// oldObj的target属性指向自身,遍历时会抛异常,该如何解决?
oldObj.target = oldObj
// 深拷贝1,这种方法本地用着可以,面试时用着不太行
function deepClone(obj) {
if(obj === null || typeof obj !== 'object') reutrn obj
let newObj = new obj.constructor
for(let key in obj) {
// ⭐️⭐️⭐️判断对象属性的引用值和当前对象的是否同一个地址
if(obj.hasOwnProperty(key) && obj[key] !== obj) {
newObj[key] = deepClone(obj[key])
}
}
return newObj
}
const obj2 = myClone(oldObj)
console.info(obj2, 'obj2')
// 深拷贝2,通过使用map对象解决循环引用问题
function deepClone2(obj, map = new Map()) {
if(obj === null || typeof obj !== 'object') return obj
let newObj = new obj.constructor
// 判断map对象中是否包含obj,包含直接返回,不包含添加
if(map.get(obj)) return map.get(obj)
map.set(obj, newObj)
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
newObj = deepClone2(obj[key], map)
}
}
return newObj
}