深拷贝

121 阅读2分钟

本文主要总结了关于深拷贝的三种实现方式

JSON.parse 和 JSON.stringify

  • 缺点
    • 函数、特殊对象、Symbol等,若为数组中的值,会变成null;非数组,会变成undefined
    • 循环引用报错
    • 对象原型被改变

递归

  • 解决的问题
    • 特殊对象类型的深拷贝:Number,String,Boolean,Map,Set,Date
    • 对象原型保持原来的
    • 循环引用和同级引用问题
    • 对象key为Symbol时,遍历出来的键值问题
  • 缺点
    • 递归容易爆栈
const checkType = (target) => {
    return Object.prototype.toString.call(target).slice(8, -1)
}

const clone = (target) => {
    const map = new WeakMap() // 解决引用问题

    const _clone = (target) => {
        if (target === null) return null
        if (typeof target !== 'object') return target

        const type = checkType(target)
        let result
        const constructor = target.constructor

        switch (type) {
            // 处理特殊对象
            case 'String':
            case 'Number':
            case 'Boolean':
            case 'Set':
            case 'Map':
                result = new constructor(target)
                break
            // 处理Date
            case 'Date':
                result = new Date(target.getTime())
                break
            // 处理数组
            case 'Array':
                result = []
                break
            // 处理对象
            case 'Object':
                const proto = Object.getPrototypeOf(target)
                result = Object.create(proto)
                break
            default:
                result = target
        }

        // 解决引用问题
        const temp = map.get(target)
        if (temp) return temp
        map.set(target, result)

        // 遍历对象属性(考虑到属性为Symbol的时候)
        const keys = Reflect.ownKeys(target)
        for (const key of keys) {
            result[key] = _clone(target[key])
        }
        return result
    }

    return _clone(target)
}

循环

  • 解决的问题
    • 不会产生递归爆栈问题
const checkType = (target) => {
    return Object.prototype.toString.call(target).slice(8, -1)
}

const initType = (target) => {
    const type = checkType(target)
    let result
    switch (type) {
        case 'Number':
        case 'String':
        case 'Boolean':
        case 'RegExp':
        case 'Set':
        case 'Map':
            const constructor = target.constructor
            result = new constructor(target)
            break;
        case 'Date':
            result = new Date(target.getTime())
            break
        case 'Array':
            result = []
            break
        case 'Object':
            const proto = Object.getPrototypeOf(target)
            result = Object.create(proto)
            break
        default:
            result = target
            break;
    }
    return result
}

const cloneLoop = (x) => {
    if (x === null) return null
    if (typeof x !== 'object') return x

    const root = initType(x)
    const map = new WeakMap()

    // 栈
    const loopList = [
        {
            parent: root, // 要复制的元素
            key: undefined, // 键值
            data: x // 键值对应的数据
        }
    ]

    while (loopList.length) {
        const node = loopList.pop()
        const { parent, key, data } = node

        let res = parent
        if (typeof key !== 'undefined') {
            res = parent[key] = initType(data)
        }

        const cache = map.get(data)
        if (cache) {
            parent[key] = cache
            continue
        }
        map.set(data, res)

        for (const k of Reflect.ownKeys(data)) {
            if (data[k] === null || typeof data[k] !== 'object') {
                res[k] = data[k]
            } else {
                loopList.push({
                    parent: res,
                    key: k,
                    data: data[k]
                })
            }
        }
    }
    return root
}