es-toolkit 的 `clone` 浅拷贝函数全解析:从 `compat` 到现代实现
2026/9/16 15:58:23 网站建设 项目流程

es-toolkit 的clone浅拷贝函数全解析:从compat到现代实现

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

clone是 es-toolkit 中用于创建对象浅拷贝(shallow copy)的核心函数。本文以 docs/ja/compat/reference/object/clone.md 为骨架,系统讲解es-toolkit/compatclone的用法、支持的数据类型、浅拷贝语义,并结合源码剖析其内部实现原理、与标准版es-toolkit/objectclone差异,以及何时应改用cloneDeepcloneWith,帮助你写出更高效、更安全的拷贝代码。

一、clone是什么:浅拷贝的基本语义

clone用于创建给定值的浅拷贝(shallow copy)。所谓浅拷贝,是指仅复制最顶层的属性,而嵌套的对象或数组会与原值共享引用。

const cloned = clone(value);

用一句话概括其行为:

  • 顶层值会生成新的实例(新数组、新对象、新 Date 等);
  • 嵌套层级不会被递归复制,内部引用保持不变。

这一语义与 Lodash 的_.clone完全兼容,因此 es-toolkit 在compat包中提供了该函数的 Lodash 兼容版本,同时也提供了面向现代开发者的精简版本(见下文「五、compat版与现代版的对比」)。

二、es-toolkit/compatclone的完整用法

2.1 基本调用方式

import { clone } from 'es-toolkit/compat'; // 原始值(primitive)的拷贝 const num = 42; const clonedNum = clone(num); // Returns: 42(同一个值) // 数组的拷贝 const arr = [1, 2, 3]; const clonedArr = clone(arr); // Returns: [1, 2, 3](新的数组实例) // 对象的拷贝 const obj = { a: 1, b: 'hello' }; const clonedObj = clone(obj); // Returns: { a: 1, b: 'hello' }(新的对象实例)

2.2 支持复制特殊对象类型

compatclone的一个重要特性是能够正确处理 JavaScript 中的各种内置对象类型:

import { clone } from 'es-toolkit/compat'; // Date 对象的拷贝 const date = new Date('2023-01-01'); const clonedDate = clone(date); // Returns: new Date('2023-01-01')(新的 Date 实例) // 正则表达式的拷贝 const regex = /hello/gi; regex.lastIndex = 3; const clonedRegex = clone(regex); // Returns: /hello/gi with lastIndex = 3(连 lastIndex 状态也会一并复制) // Map 的拷贝 const map = new Map([ ['a', 1], ['b', 2], ]); const clonedMap = clone(map); // Returns: new Map([['a', 1], ['b', 2]]) // Set 的拷贝 const set = new Set([1, 2, 3]); const clonedSet = clone(set); // Returns: new Set([1, 2, 3])

注意正则表达式的细节:compat版在复制RegExp时,不仅复制sourceflags,还会保留lastIndex状态。这意味着当你对RegExp使用了带gy标志的exec/test后,其匹配位置信息也能被忠实地复制。

2.3 浅拷贝的关键特征:嵌套对象共享引用

import { clone } from 'es-toolkit/compat'; const nested = { a: 1, b: { c: 2, }, }; const clonedNested = clone(nested); console.log(clonedNested !== nested); // true(是不同的对象) console.log(clonedNested.b === nested.b); // true(嵌套对象是同一个引用)

这是浅拷贝与深拷贝最本质的区别。如果你需要连嵌套对象也一起复制,应改用cloneDeep(见下文「六、与cloneDeepcloneWith的协同使用」)。

2.4 参数与返回值

参数

  • valueT):要复制的值。可以是对象、数组、DateRegExpMapSetArrayBufferDataView、类型化数组(TypedArray)、arguments对象、Symbol 包装对象以及各类原始值。

返回值

  • T):复制后的值。对于原始值,直接返回原值本身。

三、compatclone的源码级实现原理

compatclone的实现位于 src/compat/object/clone.ts。它的实现思路是按值的类型分派处理,整体流程可以概括为以下几步:

3.1 第一步:原始值直接返回

export function clone<T>(obj: T): T { if (isPrimitive(obj)) { return obj; } // ... }

代码首先调用 src/predicate/isPrimitive.ts 中的isPrimitive判断是否为原始值。stringnumberbooleannullundefinedsymbolbigint等原始值本身不可变,无需复制,直接返回即可。

3.2 第二步:通过getTag识别对象类型

对于非原始值,实现通过getTag(obj)(来自 src/compat/_internal/getTag.ts)获取对象的内部标签(如[object Date][object Map]等),标签常量定义在 src/compat/_internal/tags.ts。

随后调用isCloneableObject做白名单校验:只有argumentsarrayarrayBufferdataViewbooleandate、各类 TypedArray、mapnumberobjectregexpsetstringsymbol等标签才被认为可克隆;函数、Promise、DOM 元素、Error 等类型不在白名单内,会直接返回{}(对应源码 src/compat/object/clone.ts 的isCloneableObject函数)。这也与测试 src/compat/object/clone.spec.ts 中「不克隆函数、async 函数、generator 函数、Proxy 构造函数、Error 系列」的用例一致。

3.3 第三步:按类型分派复制

isCloneableObject校验通过后,源码按类型逐一处理:

类型复制方式说明
数组ArrayArray.from(obj)生成新数组;若为RegExp.exec的结果(含index/input属性),会额外复制这两个属性(src/compat/object/clone.ts)
类型化数组 TypedArray用原构造器基于同一buffer重建保持byteOffsetlength,与buffer共享底层内存(src/compat/object/clone.ts)
ArrayBuffernew ArrayBuffer(byteLength)复制字节长度,但内容不复制(保持 Lodash 兼容行为)
DataView复制字节到新 buffer 再重建底层数据是真正复制的,修改副本不影响原值
Boolean/Number/String包装对象用原构造器基于valueOf()重建会同时复制自有属性(expando 属性),字符串包装对象还会复制length之外的字符串属性(src/compat/object/clone.ts)
Datenew Date(Number(date))复制时间戳
RegExp复制source+flags,并保留lastIndex见 src/compat/object/clone.ts
Symbol包装对象Object(Symbol.prototype.valueOf.call(obj))复制符号值包装对象
Map新建Map并遍历forEach逐项set键值引用不变(浅拷贝)
Set新建Set并遍历forEach逐项add元素引用不变(浅拷贝)
arguments对象复制自有属性、length,并保留Symbol.iterator见 src/compat/object/clone.ts
普通对象copyPrototype+copyOwnProperties+copySymbolProperties复制原型、自有属性与可枚举的 Symbol 属性

普通对象的复制由三个辅助函数协作完成(src/compat/object/clone.ts):

  • copyOwnProperties:遍历for...in并配合Object.hasOwn复制自有属性;
  • copySymbolProperties:通过Object.getOwnPropertySymbols复制可枚举的 Symbol 属性;
  • copyPrototype:通过Object.setPrototypeOf还原原型链,使clone(new Foo()) instanceof Foo仍为true

从源码结构看,这套实现刻意对齐了 Lodash 的_.clone行为——包括arguments对象的Symbol.iterator保留、正则lastIndex保留、TypedArray 共享 buffer 等细节,都是为 Lodash 兼容场景而设计,因此也相对更重、更慢。

3.4 测试用例验证

src/compat/object/clone.spec.ts 提供了完整的兼容性测试覆盖,可以验证上述行为:

  • 浅拷贝语义:expect(actual).not.toBe(array)expect(actual[0]).toBe(array[0])(L8-L15);
  • arguments对象复制(含Symbol.iterator,L17-L42);
  • DateMapSet、正则(含lastIndex)、数组的复制(L80-L169);
  • ArrayBuffer按字节长度复制、DataView内容真实复制且互不影响(L197-L267);
  • 正则exec结果数组的index/input属性保留(L269-L275);
  • 自定义类实例复制后仍保留原型(instanceof Footrue,L311-L331);
  • Symbol 属性只复制可枚举部分,不可枚举的 Symbol 不复制(L411-L434);
  • 函数、Error、DOM 元素等不可克隆类型返回{}(L499-L548)。

四、compat版的警告:优先使用现代版clone

compat 版文档 开头有一条醒目的警告:

这个clone函数由于包含处理特殊对象类型的复杂逻辑而相对较慢。请使用更快速、更现代的 es-toolkit 的clone

也就是说,如果你只是需要浅拷贝,且不依赖 Lodash 兼容语义,官方建议直接使用标准包的 clone(从es-toolkit/object导入)。compat包本身定位是「从 Lodash 迁移的兼容层」,其价值在于行为对齐,而非极致性能。

五、compat版与现代版的对比

es-toolkit 同时提供了两个版本的clone

维度es-toolkit/compatclonees-toolkit/objectclone
文档compat 版现代版
源码src/compat/object/clone.tssrc/object/clone.ts
定位与 Lodash_.clone行为完全对齐现代、精简、性能优先
原始值直接返回原值直接返回原值
数组新数组实例,额外保留index/input新数组实例(slice(0)
TypedArray / ArrayBuffer按类型精细处理统一用slice(0)复制
Map/Set/Date分派处理new Constructor(obj)一行完成
RegExp复制source/flags/lastIndex复制source/flags/lastIndex
Error不克隆,返回{}会复制 message、cause、stack 等
File(浏览器环境)不支持支持(复制 name、type、lastModified)
原型保留通过copyPrototype实现通过Object.create(prototype)+Object.assign实现

从源码对比可以看出(src/object/clone.ts),现代版用更少的代码覆盖了同样的核心类型:DateMapSet统一走new Constructor(obj)RegExp单独处理lastIndexError与浏览器File也都有专门分支。相比之下,compat版为了对齐 Lodash 在arguments、包装对象、Symbol 属性等边界场景的行为,逻辑分支明显更多、执行路径更长——这正是文档警告「相对较慢」的原因。

选型建议

  • 从 Lodash 迁移、需要逐行替换_.clone的项目,用es-toolkit/compat
  • 新项目或追求性能与包体积的场景,用es-toolkit/object(或直接import { clone } from 'es-toolkit')。

六、与cloneDeepcloneWith的协同使用

compat包在对象复制方向上还提供了深拷贝与自定义克隆两个相关函数,它们共享同样的 Lodash 兼容定位:

  • cloneDeep:递归复制所有层级,嵌套对象与数组都会生成新实例。当你的数据是多层嵌套结构、且需要完全隔离时,应使用cloneDeep而非clone
  • cloneWith:允许传入自定义克隆函数,在默认复制行为之外注入自定义逻辑,适合需要定制拷贝策略(如按类型做特殊处理)的场景。

它们对应的现代版(es-toolkit/object的 cloneDeep、cloneDeepWith)同样遵循「现代版更精简、compat 版更对齐 Lodash」的整体设计原则。

七、实战要点速查

  1. 确认语义clone是浅拷贝,嵌套对象共享引用;需要完全隔离时改用cloneDeep
  2. 原始值无需复制string/number/boolean/null/undefined/symbol/bigint直接返回原值。
  3. 内置类型安全复制DateRegExp(含lastIndex)、MapSetArrayBufferDataView、TypedArray、arguments对象均可正确处理。
  4. 不可克隆类型compat版对函数、Error、Promise、DOM 元素等返回{},切勿用于这些类型。
  5. 优先现代版:无 Lodash 兼容需求时,使用es-toolkit/objectclone,更简洁也更快。
  6. 类型签名clone<T>(value: T): T,入参与返回值类型一致,天然保持类型安全。

结合本文的用法示例、源码解析与测试证据,你可以准确判断在什么场景使用clone、什么场景该升级为cloneDeep,并理解compat版与现代版各自的取舍,从而在项目中写出语义清晰、性能合适的拷贝代码。

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询