功能说明
该示例为需要深拷贝但不能假定运行时支持 structuredClone 的场景提供统一入口。运行环境有原生实现时直接委托,保留其完整语义与性能;没有时使用手写回退,支持普通对象、数组、日期、正则、错误、Map、Set、ArrayBuffer、DataView 与类型化数组,并用 WeakMap 处理循环引用。
关键代码
nativeClone 通过 globalThis 的可选类型断言探测函数,避免依赖特定 TypeScript lib 声明。回退函数先处理原始值与函数、Symbol,再查 WeakMap 判断是否已复制。容器在递归子元素前先登记到 seen,因此自引用结构能正确指回副本。类型化数组按 byteOffset 与 byteLength 切片,避免把整个底层 buffer 带入;Map、Set 与普通对象都先创建空副本再填充。
使用注意
原生 structuredClone 与回退在边缘类型上不完全一致,例如 Error 的 cause、访问器属性、不可枚举属性与 SharedArrayBuffer 的处理。回退遇到访问器或类实例会抛 CloneError,而不是静默丢失数据;无法克隆函数与 Symbol 与原生行为一致。跨 realm 对象可能因原型不同被拒绝。不要把该函数用于含大量数组的结构而不评估内存,两倍复制仍然存在。
验证方式
在强制走回退的测试中克隆嵌套对象与数组,断言深度相等且引用不同;构造自引用对象验证循环被保留;克隆 Date、RegExp、Map、Set 与带偏移的 Uint8Array,检查值与类型;修改副本不影响原对象;传入函数或自定义类实例断言 CloneError;当运行环境存在原生实现时,验证 deepClone 结果同样正确。
代码示例
export class CloneError extends Error {
constructor(message: string) {
super(message);
this.name = "DataCloneError";
}
}
function nativeClone<T>(value: T): T | undefined {
const candidate = (globalThis as { structuredClone?: <U>(input: U) => U }).structuredClone;
return typeof candidate === "function" ? candidate(value) : undefined;
}
export function deepClone<T>(value: T): T {
const cloned = nativeClone(value);
return cloned === undefined ? (cloneFallback(value, new WeakMap()) as T) : cloned;
}
function cloneFallback(value: unknown, seen: WeakMap<object, unknown>): unknown {
if (value === null) return null;
const type = typeof value;
if (type === "function" || type === "symbol") {
throw new CloneError("value of type " + type + " cannot be cloned");
}
if (type !== "object") return value;
const object = value as object;
const existing = seen.get(object);
if (existing !== undefined) return existing;
if (value instanceof Date) return new Date(value.getTime());
if (value instanceof RegExp) return new RegExp(value.source, value.flags);
if (value instanceof Error) {
const copy = new Error(value.message);
copy.name = value.name;
copy.stack = value.stack;
seen.set(object, copy);
return copy;
}
if (value instanceof ArrayBuffer) return value.slice(0);
if (value instanceof DataView) {
const buffer = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
return new DataView(buffer);
}
if (ArrayBuffer.isView(value)) {
const view = value as unknown as {
buffer: ArrayBuffer;
byteOffset: number;
byteLength: number;
constructor: new (buffer: ArrayBuffer) => object;
};
const buffer = view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
return new view.constructor(buffer);
}
if (value instanceof Map) {
const copy = new Map<unknown, unknown>();
seen.set(object, copy);
for (const [key, entry] of value) {
copy.set(cloneFallback(key, seen), cloneFallback(entry, seen));
}
return copy;
}
if (value instanceof Set) {
const copy = new Set<unknown>();
seen.set(object, copy);
for (const entry of value) copy.add(cloneFallback(entry, seen));
return copy;
}
if (Array.isArray(value)) {
const copy: unknown[] = [];
seen.set(object, copy);
for (const item of value) copy.push(cloneFallback(item, seen));
return copy;
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== Object.prototype && prototype !== null) {
throw new CloneError("unsupported object type");
}
const copy: Record<PropertyKey, unknown> = Object.create(prototype);
seen.set(object, copy);
for (const key of Reflect.ownKeys(object)) {
const descriptor = Object.getOwnPropertyDescriptor(object, key);
if (!descriptor || !descriptor.enumerable) continue;
if (!("value" in descriptor)) {
throw new CloneError("accessor properties cannot be cloned");
}
copy[key] = cloneFallback(descriptor.value, seen);
}
return copy;
}