类数组克隆需先识别其结构(非负整数索引+合法length),再用for循环按索引浅拷贝,避免for...in;返回类型依需求选Array、原构造器或plain object,不可枚举属性不影响索引访问。

自定义类数组(即实现了 ArrayLike 行为但非原生 Array 的对象,如 arguments、NodeList 或手动构造的带 length 和数字键的对象)在克隆时不能直接用 Array.from() 或展开运算符,需兼顾结构、类型与可枚举性。关键在于识别类数组特征、安全遍历、保持原型链或构造器语义。
识别并校验类数组结构
类数组的核心标识是:拥有非负整数索引属性 + 可读的 length 属性(且 length 为有限非负整数)。需避免误判普通对象或稀疏/非法 length:
- 检查
obj != null且typeof obj.length === 'number' - 验证
Number.isInteger(obj.length) && obj.length >= 0 && !isNaN(obj.length) - 不依赖
Symbol.isConcatSpreadable或Array.isArray(),它们对类数组返回false
按索引顺序逐项浅拷贝
类数组的“元素”仅存在于 0 到 length - 1 的整数键上,不能用 for...in(会遍历原型和非数字键):
- 用
for (let i = 0; i < obj.length; i++)显式迭代 - 每次访问
obj[i]并赋值到新容器中(如新建数组或目标对象) - 注意:若
obj是稀疏的(如{0: 'a', 2: 'c', length: 3}),obj[1]为undefined,应保留该值而非跳过
决定返回类型与构造语义
克隆结果是否要“像原对象”?这取决于用途:
- 统一返回
Array:最安全通用,可用Array.from(obj)(它内部已处理类数组逻辑)或手写循环push - 保持原构造器:若需继承行为(如自定义类数组有
map方法),需调用其constructor,但须确保构造器支持length和索引初始化(多数不支持,慎用) - 返回 plain object 类数组:新建空对象,设置
length,再赋值各索引项(适合只传参场景,如模拟arguments)
处理不可枚举或 accessor 属性(进阶)
标准类数组(如 NodeList)通常所有索引属性都是可枚举的;但若自定义类数组使用 Object.defineProperty 定义索引键且设为 enumerable: false,则常规 for 循环仍可访问(因是数值属性访问,非枚举遍历),无需额外处理。真正需关注的是:只读属性或 getter —— 克隆时取其当前值即可,不保留 getter 逻辑,除非显式代理。

















