TS实现深拷贝(包含ES6的Map和Set类型)

458 阅读3分钟

深度解析 TypeScript 中的深度克隆函数

在复杂的软件开发场景下,深度克隆技术对于复制含有嵌套结构或特殊类型对象至关重要。以下是一个精心设计的 TypeScript 函数,实现了深度克隆功能,能妥善处理循环引用、日期、正则表达式乃至函数对象

函数详情

export function deepClone<T>(target: T): T {
    const map = new WeakMap();
    const stack = new Set<unknown>();

    function isObject(obj: unknown): obj is object {
        return typeof obj === 'object' && obj !== null;
    }

    function cloneData(data: unknown): unknown {
        if (!isObject(data)) return data;
        if (data instanceof Date) return new Date(data);
        if (data instanceof RegExp) return new RegExp(data.source, data.flags);
        if (typeof data === 'function') return generateSafeFunction(data as Function);

        if (stack.has(data)) {
            throw new Error('Cannot clone object with circular reference');
        }
        stack.add(data);

        const exist = map.get(data);
        if (exist) return exist;

        if (data instanceof Map) {
            const result = new Map();
            map.set(data, result);
            data.forEach((value, key) => {
                result.set(key, cloneData(value));
            });
            return result;
        }

        if (data instanceof Set) {
            const result = new Set();
            map.set(data, result);
            data.forEach(value => {
                result.add(cloneData(value));
            });
            return result;
        }

        const keys = Reflect.ownKeys(data);
        const allDesc = Object.getOwnPropertyDescriptors(data);
        const result = Object.create(Object.getPrototypeOf(data), allDesc);
        map.set(data, result);

        keys.forEach((key: PropertyKey) => {
            const value = data[key as keyof typeof data];
            result[key] = isObject(value) ? cloneData(value) : value;
        });

        stack.delete(data);
        return result;
    }

    function generateSafeFunction(fn: Function): Function {
        const code = fn.toString();
        const bodyStart = code.indexOf('{') + 1;
        const bodyEnd = code.lastIndexOf('}');
        const fnBody = code.slice(bodyStart, bodyEnd);
        const newFnCode = `return function ${fn.name ?? 'anonymous'}() { ${fnBody} }`;
        return new Function(newFnCode)();
    }

    return cloneData(target) as T;
}

函数功能详述

核心目标

  • 泛型实现:提供泛型参数 T,使得该函数可以应用于任何类型对象的深度复制。
  • 全面支持:有效处理各种复杂类型,包括普通对象、数组、MapSetDateRegExp 以及包含函数的对象。
  • 循环引用:智能识别并处理对象间的循环引用问题,避免栈溢出错误。

关键实现步骤

1. 辅助数据结构

  • WeakMap: 用于存储已遍历对象与其克隆副本的映射关系,有助于处理循环引用。
  • Set (stack): 记录当前遍历路径上的对象,一旦发现重复对象即表示存在循环引用。

2. 类型检测与基础处理

  • isObject 函数:确保只对真正的对象类型进行处理,排除原始类型。
  • 特殊类型处理:针对 DateRegExp 和函数,采取特定逻辑创建对应的新实例。

3. 递归与集合类型处理

  • cloneData 函数:递归地遍历对象属性,执行深度复制。
  • 集合类型 (Map, Set):创建新实例,并遍历原集合元素进行逐一克隆。

4. 循环引用检测与处理

  • stack 集合:检测到对象已在遍历路径上时抛出错误,避免无限循环。

5. 函数复制

  • generateSafeFunction:通过解析函数字符串并重新封装,安全地生成函数副本,保持独立性。
const original = {
    number: 42,
    text: "Hello, World!",
    complex: new Date(),
    regex: /hello/i,
    nested: { deeper: true },
    selfRef: null, // 假设最终指向自身,模拟循环引用
};
const cloned = deepClone(original);
// cloned 现在是 original 的完全独立副本

结语

此深度克隆函数是 TypeScript 开发者应对复杂数据结构复制需求的强大工具,其设计考虑周全,能有效提升代码的健壮性和可维护性。无论是状态管理、数据预处理还是其他需要精确复制对象的场景,该函数都能发挥重要作用