先记住这个答案
Array.from 可以消费同步可迭代对象,也可以在没有可用迭代方法时按类数组的 length 和数字索引读取。数组字面量中的展开则要求可迭代协议,仅有 length 并不够。如果对象同时提供有效 Symbol.iterator 和类数组属性,Array.from 优先使用迭代路径,结果不必等于那些索引属性。它创建新数组但仍是浅拷贝,缺失位置会成为可见的 undefined。普通对象键值枚举应使用 Object.entries 等接口,异步迭代也不能直接当作同步输入处理。
- 类数组路径与迭代路径是两套输入契约
- 数组展开不会只凭 length 开始读取
- 转换成新数组不会克隆每一个对象元素
只提供索引和长度时不能直接展开
示例的对象有零号和二号属性,中间缺少一号。Array.from 可以按长度生成三个实际数组位置;数组展开找不到迭代方法,因此失败。输出时显式检查位置存在,避免 JSON 把 undefined 转成 null 掩盖区别。
这类差异常出现在手工构造的测试对象或某些宿主集合上。不要依据控制台外观判断可展开性,应检查接口文档与实际 Symbol.iterator;arguments 可迭代不代表所有类数组都可迭代。
const indexed = { 0: 'A', 2: 'C', length: 3 };
const values = Array.from(indexed);
console.log(values.length, 1 in values, values[1] === undefined);
try { [...indexed]; } catch (error) { console.log(error.name); }
const both = { 0: 'indexed', length: 1,
[Symbol.iterator]: function* () { yield 'iterated'; }
};
console.log(Array.from(both).join(','));查看输出与解释
3 true true
TypeError
iterated中间位置被实际创建为 undefined,而非保留空位;同时具备迭代器时读取 iterated,说明 Array.from 不会先按 length 复制完再追加迭代结果。
转换前确定规模和是否允许副作用
类数组的 length 与索引可能是访问器,读取时可以执行代码;迭代器也可以有自定义逻辑甚至永不结束。因此转换不应被当作对任意输入都便宜而无副作用的安全操作。
处理外部配置或大集合时,应先校验允许的数据形状与规模。若只需要前几项,可以选择有界读取,避免为了使用数组方法把整个惰性数据源一次性物化,造成额外内存和等待。
新容器不等于深复制或异步完成
对象元素仍指向原对象,转换后修改 values[0].name 可能影响源集合中的同一个对象。需要快照时应明确哪些层级要复制,不能把 Array.from 作为通用深拷贝工具。
同步 Array.from 不会自动等待异步迭代协议或把所有 Promise 元素解析成最终值。异步来源需要对应异步消费方式,并考虑错误、取消和并发预算;转换容器与等待任务完成是两个动作。
容易答错的地方
- 把普通对象属性当作类数组自动收集
- 没有合适 length 或迭代契约的记录对象,不会按业务键自动转成值数组。应明确用 Object.keys、Object.values 或 Object.entries 表达想要的结果,再决定是否继续映射。
- 认为展开和 Array.from 对所有对象等价
- 它们在类数组输入、构造与映射能力等方面存在差异。即使常见数组和 Set 得到相同结果,也不能外推到只有数字索引的对象或自定义迭代器,需要按输入协议分别验证。
面试官还会怎么问?
有迭代器但 length 为零会得到空数组吗?
有效迭代方法会被优先使用,因此不能仅凭 length 推断结果。迭代器实际产生多少项决定迭代路径的输出;若应用要求长度上限,需要自己约束数据源或采用有界消费。
Array.from 会保留数组空位吗?
通常不会,转换会把相应读取结果写入新数组,缺失索引成为显式 undefined。若业务依赖属性缺失而非值为空,需要选择保留结构的复制方式,并通过属性存在性断言验证。
转换 DOM 集合就代表节点被复制了吗?
不代表,结果数组保存原 DOM 节点的引用,节点本身仍是同一对象。数组可以作为集合快照,但节点的属性和所属文档仍可能变化;复制容器与复制节点是两层不同操作。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。