克隆辅助函数应精简:仅深处理对象和数组,基础类型直接返回,省略WeakMap循环检测、原型继承及特殊类型支持,优先使用[...arr]和{...obj}等原生API,体积小且性能高。

写一个既小又快的克隆辅助函数,关键不是堆功能,而是精准识别哪些类型必须深处理、哪些可以直接透传,同时避开循环引用和构造器陷阱。
只克隆真正需要隔离的数据结构
多数场景下,你并不需要克隆 Date、RegExp、Map、Set——如果业务里压根不出现这些类型,硬加支持只会增大体积、拖慢主路径。klona 的 lite 版(240B)就只处理对象、数组、null 和基础类型,正是这个思路的体现:
- 字符串、数字、布尔、undefined、null:直接返回,不进递归
- 普通对象({})和数组([]):用
Object.create或[]新建,再逐属性克隆 - 其他所有类型(如 Date、RegExp、Function):原样返回,不做处理
用 WeakMap 防循环引用,但只在必要时启用
循环引用检测会带来额外开销和内存占用。如果你能确定输入数据是“树状”而非“图状”(比如纯 JSON 数据、表单状态、配置对象),完全可以省略 WeakMap 缓存逻辑。下面这个简化版就去掉了它:
function clone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(clone);
}
const out = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
out[key] = clone(obj[key]);
}
}
return out;
}
它只有 120 行内(压缩后约 320B),对无嵌套或浅嵌套对象效率极高,且完全避免了 Map 初始化和查找成本。
按需支持原型继承,不默认兜底
很多克隆函数默认用 Object.create(Object.getPrototypeOf(obj)) 保留原型链,但这在大多数业务对象中是冗余的——ES6 class 实例、带方法的对象通常不该被随意克隆;若真需要,应由调用方显式处理。轻量函数可约定:只保值、不保行为。这样就不必调用 getPrototypeOf,也不用处理 constructor 或 __proto__ 字段。
- 普通字面量对象 → 用
{}创建,不关心原型 - 想保留原型?调用方自己做:
Object.assign(Object.create(Original.prototype), clone(obj)) - 类实例克隆属于领域逻辑,不应由通用工具越界承担
优先用原生 API,少写分支判断
比如数组克隆,[...arr] 比 arr.slice() 更快更短;对象展开 {...obj} 在无 getter/setter 时也足够安全。轻量函数可把这类原生能力作为默认路径,仅在必要时降级为手动遍历:
- 对数组:优先
Array.isArray(obj) ? [...obj] : ... - 对纯对象(无 symbol、不可枚举属性):用
{...obj}替代 for-in 循环 - 只在需要兼容不可枚举属性或 symbol 键时,才引入
Reflect.ownKeys分支










