JavaScript Array.from 的双入口:length 写了二,为什么仍然读出三项

前天 3阅读

接入一个外部集合时,看见对象有零、一两个索引和 length: 2,容易预期 Array.from 只复制这两项。可是对象还可能提供 Symbol.iterator;两套入口同时存在时,Array.from 会先使用迭代器,结果数量由它实际产出的值决定。

这会影响集合适配器、测试替身和第三方组件之间的数据交换。调试时只展开对象属性,可能看到的是索引版本,转换成数组后拿到的却是另一组值。下面把两套内容故意写得不同,并给 length 加上读取计数,让入口选择可以直接验证。

程序在 Node.js v24.19.0 上实跑,使用 ECMAScript 的 Array.from 和同步迭代协议。保存为 demo.mjs,执行 node demo.mjs。所有对象都在内存里,代码中的属性修改仅用于本次实验。

JavaScript Array.from 的双入口:length 写了二,为什么仍然读出三项

AI模型生成概念示意:活动的珠子通道进入接收托盘,旁边的索引架保持独立,表示转换选择了一条元素来源;不是软件截图或运行结果。

完整实验程序

import assert from 'node:assert/strict';

let lengthReads = 0;
const source = {
  0: 'index-A',
  1: 'index-B',
  get length() { lengthReads++; return 2; },
  *[Symbol.iterator]() { yield 'iter-X'; yield 'iter-Y'; yield 'iter-Z'; }
};

const iterated = Array.from(source);
assert.deepEqual(iterated, ['iter-X', 'iter-Y', 'iter-Z']);
assert.equal(lengthReads, 0);
console.log('iterator', JSON.stringify(iterated), 'length-reads', lengthReads);

source[Symbol.iterator] = undefined;
const indexed = Array.from(source);
assert.deepEqual(indexed, ['index-A', 'index-B']);
assert.equal(lengthReads, 1);
console.log('indexed', JSON.stringify(indexed), 'length-reads', lengthReads);

source[Symbol.iterator] = 7;
assert.throws(() => Array.from(source), TypeError);
console.log('non-callable iterator', 'TypeError');

const numericKeysOnly = { 0: 'A', 1: 'B' };
assert.deepEqual(Array.from(numericKeysOnly), []);
console.log('missing length', JSON.stringify(Array.from(numericKeysOnly)));

本地实际输出

iterator ["iter-X","iter-Y","iter-Z"] length-reads 0
indexed ["index-A","index-B"] length-reads 1
non-callable iterator TypeError
missing length []

三个迭代值出现时,length 根本没有被读取

第一行结果包含 iter-X、iter-Y、iter-Z,计数是零。source 的数字索引虽然存在,但这次转换不靠它们取值,也没有先读取 length 再决定迭代多少次。若在 length 中放日志、计数或昂贵计算,这条路径同样可能完全不执行它。

第二段把这个本地对象的 Symbol.iterator 设为 undefined,转换才转入类数组路径。结果是 index-A、index-B,length 的读取计数增加到一。这里的 undefined 表示没有可用迭代方法,并不是一个返回空序列的迭代器;真正返回空序列的迭代器会优先产生空数组。

这不是建议在业务中直接修改第三方集合。若接口明确要求复制其索引区间,应通过自己控制的适配对象或显式索引循环表达这一契约;若要求接受任意可迭代值,就应以迭代器产出的顺序和终止条件为准。

无效方法不会自动退回索引路径

第三段把 Symbol.iterator 设为数字七,Array.from 抛出 TypeError。它不会因为这个属性不能调用,就当它不存在并继续读索引。缺失协议和损坏协议是两种不同状态,容错代码若一概改成空数组,就会把接口错误藏成“没有数据”。

最后的对象只有数字键,没有 length,也没有迭代器,结果为空数组。Array.from 不会数一遍对象上有多少个数字形状的键来推算长度。把普通键值记录误传到集合接口时,这个结果尤其容易漏报。

转换还会实际执行迭代方法或属性读取。对于会产生副作用、数量巨大或永不结束的输入,不应把 Array.from 当成无成本的类型检查。先明确输入来源是否有限、是否允许重复读取,再把它收集成完整数组;测试至少保留本文这四种入口情况。

参考资料

官方资料核验日期:2026-10-02。以上输出来自文中完整程序,断言通过,退出状态为零。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。