用TypeScript手写一个深拷贝函数

1,884 阅读2分钟

深拷贝是前端开发中经常用到的技术,其目的是为了克隆一个与原始对象完全独立的对象。说起来你可能不信,其实没有一个深拷贝可以复制所有数据类型,我们能做的, 就是实现大多数的类型,理解其原理, 了解其背后的知识网络,才是写他的意义。

数据类型

  • 在 JavaScript 中,数据类型可以分为基本数据类型和引用数据类型。
    基本数据类型:String, Number, Boolean, Null, Undefined
    引用数据类型:Array, Object, Function ...
  • 不同数据类型的判断

使用 typeof 运算符来判断基本数据类型,使用 Object.prototype.toString.call() 来判断引用数据类型。

类型赋值

  • 基本类型赋值
  • 对象的属性访问和遍历方式(不可迭代类型赋值)
  • 数组的遍历方式
  • Set 和 Map 数据结构及其遍历方式
  • 循环引用的处理方法
  • 类型判断的方法
  • 构造函数赋值方式(是否需要new,new是否有参数)
  • TS类型控制
export enum CloneType {
    Object = "Object",
    Array = "Array",
    Date = "Date",
    RegExp = "RegExp",
    Function = "Function",
    String = "String",
    Number = "Number",
    Boolean = "Boolean",
    Undefined = "Undefined",
    Null = "Null",
    Symbol = "Symbol",
    Set = "Set",
    Map = "Map"
}

export type _CloneType = keyof typeof CloneType

/**
 * 检测数据类型
 * @param type cloneType
 * @param obj 检测的数据源
 * @returns Boolean
 */
function isType<T>(type: _CloneType, obj: T) {
    return Object.prototype.toString.call(obj) === `[object ${type}]`;
}

/**
 * 深拷贝
 * @param obj 要克隆的对象
 * @param cache 缓存对象,用于解决循环引用的问题
 *  */
export function cloneDeep<T>(obj: T, cache = new WeakMap()): T {
    // 如果不是对象或者是null,直接返回(终止条件)
    if (typeof obj !== 'object' || obj === null) {
        return obj
    }

    // 如果类型是Symbol,直接返回一个新的Symbol
    if (isType(CloneType.Symbol, obj)) {
        return obj.constructor((obj as unknown as Symbol).description)
    }
    // 如果已经缓存过,直接返回缓存的值
    if (cache.has(obj)) {
        return cache.get(obj)
    }

    // 初始化返回结果
    let temp: T, param: T
    // 如果是日期对象,直接返回一个新的日期对象
    if (isType(CloneType.Date, obj) || isType(CloneType.RegExp, obj)) {
        param = obj
    }
    // @ts-ignore
    temp = new obj!.constructor(param)
    // 如果是数组或者对象,需要遍历
    if (isType(CloneType.Array, obj) || isType(CloneType.Object, obj)) {
        Object.keys(obj)
            .forEach(key => {
                if (obj.hasOwnProperty(key)) {
                    temp[key] = cloneDeep(obj[key], cache)
                }
            })
    }
    // 如果是Set
    if (isType(CloneType.Set, obj)) {
        for (let value of (obj as unknown as Set<T>)) {
            (temp as Set<T>).add(cloneDeep(value, cache))
        }
    }
    // 如果是Map
    if (isType(CloneType.Map, obj)) {
        for (let [key, value] of (obj as unknown as Map<T, T>)) {
            (temp as Map<T, T>).set(cloneDeep(key, cache), cloneDeep(value, cache))
        }
    }
    // 缓存值
    cache.set(obj, temp)
    return temp
  }

}