Skip to content

deepClone 深拷贝函数

在复杂数据结构(对象、数组、Map、Set、Date、RegExp、TypedArray 等)以及存在循环引用的场景中,深拷贝可生成一个与源数据相独立的完整副本。

安装与引入

按需导入(推荐)

ts
import { deepClone } from '@cuixingjian/cui-utils'

子路径导入

ts
import { deepClone } from '@cuixingjian/cui-utils/deepClone'

使用示例

基础对象与数组拷贝

ts
import { deepClone } from '@cuixingjian/cui-utils/deepClone'

// 1) 基础对象/数组拷贝
const source = {
  id: 1,
  name: 'cui',
  list: [1, 2, { a: 3 }],
  date: new Date(),
  re: /abc/gi
}
const copy = deepClone(source)

console.log(copy !== source) // true
console.log(copy.list !== source.list) // true
console.log(copy.date.getTime() === source.date.getTime()) // true
console.log(copy.re.source === source.re.source) // true
console.log(copy.re.flags === source.re.flags) // true

循环引用拷贝

ts
const a: any = { name: 'A' }
const b: any = { name: 'B' }
a.ref = b
b.ref = a

const cloned = deepClone(a)
console.log(cloned !== a) // true
console.log(cloned.ref !== b) // true
console.log(cloned.ref.ref === cloned) // true(循环结构保持)

Map/Set/TypedArray 拷贝

ts
const m = new Map<any, any>([[{ k: 1 }, { v: 1 }]])
const s = new Set<any>([{ id: 1 }, { id: 2 }])
const ta = new Uint8Array([1, 2, 3])

const m2 = deepClone(m)
const s2 = deepClone(s)
const ta2 = deepClone(ta)

console.log(m2 !== m) // true
console.log(s2 !== s) // true
console.log(ta2 !== ta) // true
console.log([...m2.keys()][0] !== [...m.keys()][0]) // true(键也被深拷贝)

API 说明

函数签名

ts
function deepClone<T>(input: T, cache?: WeakMap<object, any>): T

参数

参数名类型默认值说明
inputT要拷贝的任意值
cacheWeakMap<object, any>new WeakMap()内部使用的缓存,用于处理循环引用

返回值

返回深拷贝后的新值,类型与输入值相同。


注意事项

支持的类型

  • 原始类型:直接返回(number、string、boolean、null、undefined、symbol、bigint)
  • Date:保留时间戳创建新实例
  • RegExp:保留 source、flags 和 lastIndex
  • ArrayBuffer:创建新的字节副本
  • DataView:创建新的视图副本
  • TypedArray:创建同类型的新数组副本
  • Map:深拷贝键和值
  • Set:深拷贝元素
  • 普通对象和数组:递归深拷贝所有属性

特殊处理

  • 循环引用:使用 WeakMap 缓存已拷贝对象,避免无限递归
  • 原型链:保留对象的原型链
  • 访问器属性:保留 get/set 访问器
  • 不可拷贝类型:函数、Promise、Error、WeakMap、WeakSet 保持原引用

典型场景

  • 状态管理:复制状态对象避免意外修改
  • 表单数据:复制表单初始值用于重置
  • 配置对象:复制配置避免污染原始配置
  • 数据快照:保存数据的某个时刻状态

源码

展开查看
ts
// 源码来自 @cuixingjian/cui-utils/deepClone
export type AnyObject = Record<string | symbol, any>;

export function deepClone<T>(input: T, cache = new WeakMap<object, any>()): T {
  if (!isObject(input)) return input;
  if (cache.has(input as object)) return cache.get(input as object);
  if (input instanceof Date) return new Date(input.getTime()) as unknown as T;
  if (input instanceof RegExp) {
    const re = new RegExp(input.source, input.flags);
    re.lastIndex = input.lastIndex;
    return re as unknown as T;
  }
  if (input instanceof ArrayBuffer) return input.slice(0) as unknown as T;
  if (input instanceof DataView) {
    const buffer = deepClone(input.buffer, cache) as ArrayBuffer;
    return new DataView(buffer, input.byteOffset, input.byteLength) as unknown as T;
  }
  if (ArrayBuffer.isView(input) && !(input instanceof DataView)) {
    // @ts-expect-error 同类型构造复制
    const Ctor = (input as any).constructor;
    return new Ctor(input) as T;
  }
  if (input instanceof Map) {
    const result = new Map<any, any>();
    cache.set(input, result);
    input.forEach((v, k) => {
      const keyClone = isObject(k) ? deepClone(k as any, cache) : k;
      const valClone = isObject(v) ? deepClone(v as any, cache) : v;
      result.set(keyClone, valClone);
    });
    return result as unknown as T;
  }
  if (input instanceof Set) {
    const result = new Set<any>();
    cache.set(input, result);
    input.forEach((v) => {
      const valClone = isObject(v) ? deepClone(v as any, cache) : v;
      result.add(valClone);
    });
    return result as unknown as T;
  }
  if (
    typeof input === 'function' ||
    input instanceof Promise ||
    input instanceof Error ||
    input instanceof WeakMap ||
    input instanceof WeakSet
  ) return input;
  if (Array.isArray(input)) {
    const arr: any[] = [];
    cache.set(input, arr);
    for (let i = 0; i < input.length; i++) {
      const val = (input as any)[i];
      arr[i] = isObject(val) ? deepClone(val as any, cache) : val;
    }
    return arr as unknown as T;
  }
  const proto = Object.getPrototypeOf(input as object);
  const result = Object.create(proto);
  cache.set(input as object, result);
  const keys = Reflect.ownKeys(input as object);
  for (const key of keys) {
    const desc = Object.getOwnPropertyDescriptor(input as object, key);
    if (!desc) continue;
    if ('value' in desc) {
      const cloned = isObject(desc.value) ? deepClone(desc.value, cache) : desc.value;
      Object.defineProperty(result, key, {
        value: cloned,
        writable: desc.writable,
        enumerable: desc.enumerable,
        configurable: desc.configurable,
      });
    } else {
      Object.defineProperty(result, key, {
        get: desc.get,
        set: desc.set,
        enumerable: desc.enumerable,
        configurable: desc.configurable,
      });
    }
  }
  return result as T;
}

function isObject(val: any): val is object {
  return (typeof val === 'object' && val !== null) || typeof val === 'function';
}