Appearance
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参数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| input | T | — | 要拷贝的任意值 |
| cache | WeakMap<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';
}