手撕对象浅拷贝、深拷贝,解决循环引用问题

132 阅读2分钟

工作中经常用到对象拷贝,但是具体实现原理并不是很懂。通过学习手撕浅拷贝深拷贝

浅拷贝

  • 浅拷贝只拷贝对象最外一层,深层次的对象引用不考虑
// 被拷贝对象
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
}