本文系统说明 JavaScript 中的:
- 迭代器协议与可迭代协议;
- 如何通过
.values()等拿到迭代器; - 全局
Iterator的静态方法 与Iterator.prototype实例方法 各自的参数、返回值与行为要点。
语法与行为以 ECMAScript 标准及 MDN 中文文档:Iterator 为准;运行环境需支持相应特性(例如较新的浏览器与 Node 22+ 对迭代器辅助方法有较好支持)。
一、两个协议:先分清「能遍历」和「怎么取下一个」
1. 迭代器协议(Iterator protocol)
对象若提供 next() 方法,且每次调用返回 迭代器结果对象,即符合迭代器协议:
ts
interface IteratorResult<T> {
done: boolean;
value?: T;
}
done === false:本次有value,序列可能未结束;done === true:迭代结束;value常为undefined(生成器在结束时可有不同约定)。
可选地,迭代器还可实现 return() 、throw()(用于提前关闭或错误传播),多见于生成器或需释放资源的场景。
2. 可迭代协议(Iterable protocol)
对象若提供 [Symbol.iterator]() 方法,且调用后返回符合迭代器协议的对象 ,则该对象是可迭代的。for...of、展开运算符 ...、Array.from 等语法都依赖这一点。
要点 :「可迭代」≠「自带 map/filter」 。普通可迭代对象只有 Symbol.iterator,没有 统一的 .map();链式惰性管道要建立在继承自 Iterator 的迭代器 (即规范中的 proper iterator )上,或通过 Iterator.from() 包装。
示例:手动 next 与 for...of
javascript
const iterable = [1, 2, 3];
const iterator = iterable[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
// for...of 内部等价于不断调用 next,直到 done === true
for (const v of [10, 20]) {
console.log(v);
}
二、如何拿到迭代器:.values() 与等价入口
1. Array.prototype.values()
- 作用 :返回一个数组迭代器 ,按索引顺序产出元素值(空槽行为与遍历语义一致,需注意稀疏数组)。
- 与默认迭代器的关系 :
arr[Symbol.iterator]()与arr.values()在标准中行为一致。
javascript
const arr = [10, 20, 30];
const it = arr.values();
it.next(); // { value: 10, done: false }
2. Map.prototype.values() / Set.prototype.values()
Map:values()只产出值 ;键需用keys()或entries()。Set:values()与keys()在 Set 上语义一致(元素即「值」)。
javascript
const m = new Map([
["a", 1],
["b", 2],
]);
console.log([...m.values()]); // [1, 2]
console.log([...m.keys()]); // ["a", "b"]
const s = new Set([7, 8, 9]);
console.log([...s.values()]); // [7, 8, 9]
3. TypedArray.prototype.values()
按索引产出类型数组中的数值,同样是 Iterator 实例(在支持的环境中可使用迭代器辅助方法)。
4. 其他常见来源
- 字符串 :
str[Symbol.iterator]()按 Unicode 码位迭代; - 生成器 :
function* g() { yield 1; }返回的生成器对象是实现迭代器协议的对象,在支持时也可与Iterator体系协作; Iterator.from(x):把可迭代对象 或裸迭代器包装成带辅助方法的迭代器(见下文静态方法)。
为何文章里常写 items.values().filter(...).take(10) :起点必须是迭代器 ;数组本身没有惰性 filter/take,所以要先 .values()(或 Iterator.from(items) 等)。
三、全局 Iterator:静态方法(参数与用法)
以下方法挂在 Iterator 构造函数上。
Iterator.from(item)
| 项目 | 说明 |
|---|---|
| 参数 | item:可迭代对象,或符合迭代器协议的对象(有 next)。 |
| 返回 | 新的迭代器实例,可在其上调用 Iterator.prototype 的辅助方法。 |
| 用途 | 把「只有 Symbol.iterator 的普通可迭代」转成带 .map()/.filter() 等的规范迭代器;或统一处理第三方返回的迭代器。 |
javascript
const it = Iterator.from([1, 2, 3]);
const doubled = it.map((x) => x * 2);
console.log([...doubled]); // [2, 4, 6]
// 普通可迭代(如自定义对象)无 .map,需 Iterator.from 再链式调用
const customIterable = {
[Symbol.iterator]() {
let n = 0;
return {
next() {
if (n < 3) return { value: n++, done: false };
return { done: true };
},
};
},
};
console.log([...Iterator.from(customIterable).map((x) => x * 10)]); // [0, 10, 20]
Iterator.concat(...iterables)
| 项目 | 说明 |
|---|---|
| 参数 | 多个可迭代对象(按顺序拼接)。 |
| 返回 | 单个迭代器:先穷尽第一个,再穷尽第二个,以此类推。 |
| 用途 | 合并多个数据源而不必先 [...a, ...b] 成大数组。 |
javascript
const merged = Iterator.concat([1, 2], ["a", "b"], []);
console.log([...merged]); // [1, 2, "a", "b"]
Iterator.zip(...iterables)
| 项目 | 说明 |
|---|---|
| 参数 | 多个可迭代对象。 |
| 返回 | 每次 yield 一组 值:[a[i], b[i], ...],长度以最短的可迭代为准(耗尽最短即结束)。 |
| 用途 | 并行对齐多列数据(类似 Python zip)。 |
javascript
const pairs = Iterator.zip([1, 2, 3], ["a", "b"]);
console.log([...pairs]); // [[1, "a"], [2, "b"]]
Iterator.zipKeyed(iterables[, options])
| 项目 | 说明 |
|---|---|
| 参数 | iterables:对象,每个属性的值 是一路可迭代序列,属性名 会出现在输出的每一行对象上。可选 options:mode(如 "shortest" / "longest" / "strict")、padding 等。 |
| 返回 | 每次产出一个「行对象」,键与 iterables 相同。 |
| 用途 | 列式表格数据按行遍历。 |
javascript
const table = {
name: ["Ann", "Bob"],
score: [90, 85],
};
const rows = Iterator.zipKeyed(table);
for (const row of rows) {
console.log(row); // { name: "Ann", score: 90 } 然后 { name: "Bob", score: 85 }
}
(更多选项见 MDN 中文文档:Iterator.zipKeyed。)
四、Iterator.prototype 实例方法:参数一览
以下方法由内置迭代器 (如 arr.values() 的返回值)继承。它们多数是惰性的 :返回新的 Iterator Helper 对象,直到被消费(for...of、toArray()、reduce() 等)才真正拉取底层数据。
回调形参约定 (与数组方法对齐的部分):凡带 callbackFn 的,通常接收 (element, index) ,index 为从 0 递增的序号(对非数组来源同样会提供一个逻辑下标)。
变换与筛选(返回新的惰性迭代器)
map(callbackFn[, thisArg])
| 参数 | 含义 |
|---|---|
callbackFn |
(currentValue, index) => newValue,对每个元素映射为一个新值。 |
thisArg |
可选;回调内 this。 |
返回:新的迭代器,逐项产出映射结果(惰性)。
filter(callbackFn[, thisArg])
| 参数 | 含义 |
|---|---|
callbackFn |
(element, index) => boolean(真值保留,假值跳过)。 |
thisArg |
可选。 |
返回:只包含通过谓词的元素的新迭代器(惰性)。
flatMap(callbackFn[, thisArg])
| 参数 | 含义 |
|---|---|
callbackFn |
(element, index) => iterableOrIterator,每个元素可展开为可迭代对象(或迭代器)。 |
thisArg |
可选。 |
返回 :将各次返回的序列扁平串接 成一个迭代器(惰性,类似数组 flatMap)。
take(limit)
| 参数 | 含义 |
|---|---|
limit |
非负整数;最多产出这么多个元素,然后结束。 |
返回 :截断后的迭代器。与参考文章中的「只要前几条」场景直接对应:凑够即停,底层不必再往后拉。
drop(limit)
| 参数 | 含义 |
|---|---|
limit |
非负整数;跳过开头这么多个元素,再开始产出。 |
返回 :跳过前 limit 个后的迭代器。与 take 组合可模拟类似 slice(start, start+count) 的惰性片段。
归约与消费(会驱动迭代,部分必须走到结束)
reduce(callbackFn[, initialValue])
| 参数 | 含义 |
|---|---|
callbackFn |
(accumulator, currentValue, index) => nextAccumulator。 |
initialValue |
可选;若省略,第一个元素作为初始累加器(与数组 reduce 规则类似)。 |
注意 :必须遍历迭代器能产生的全部元素 才能给出最终结果,因此是急切消费 整段输入的(参考文章也提到:reduce 与「惰性、早停」目标不同)。
toArray()
| 参数 | 无 |
|---|---|
| 返回 | Array,包含迭代器将产出的所有元素(直到 done)。 |
注意 :若底层是无限迭代器且未先 take 等截断,会导致无法结束或内存问题。
forEach(callbackFn[, thisArg])
| 参数 | 含义 |
|---|---|
callbackFn |
(element, index) => void |
thisArg |
可选。 |
行为:为副作用遍历;会消费迭代器直到结束(除非内部抛错)。
谓词与查找(可能提前结束)
find(callbackFn[, thisArg])
| 参数 | 含义 |
|---|---|
callbackFn |
(element, index) => boolean |
返回 :第一个 使回调为真的 value;若无则 undefined。适合「无限序列里找第一个满足条件的项」------不必先转数组。
some(callbackFn[, thisArg])
返回 :若存在元素使回调为真则 true,否则在耗尽后为 false。可能提前结束。
every(callbackFn[, thisArg])
返回 :若遇到使回调为假的元素则 false,若全部通过则 true。可能提前结束。
其他
[Symbol.iterator]()
返回 :迭代器自身。因此迭代器可用于 for...of、展开等。
[Symbol.dispose]()
在支持 显式资源管理 (using / await using)的环境中,用于调用底层 return() 等,释放资源。与普通业务遍历关系较小,了解即可。
五、与数组方法的对比(呼应参考文章)
| 维度 | 数组方法(如 filter/map/slice) |
迭代器辅助方法 |
|---|---|---|
| 执行时机 | 调用后常立即生成新数组 | map/filter/take 等多为惰性,按需 next |
| 中间存储 | 易有多段大数组 | 通常不保留整段中间数组 |
| 提前结束 | slice 前仍可能已整表 filter/map |
take/find/some 等可在满足条件后停止 |
reduce |
数组上急切归约 | 迭代器上同样需完整消费 |
实践建议 (与原文一致):不需要整份数组时,避免「先整表变数组再切」;需要惰性时用 values()(或 Iterator.from)+ filter/map/take 等描述管道。
示例:同逻辑下数组链与迭代器链的 filter 调用次数(说明「提前结束」)
javascript
const nums = Array.from({ length: 1000 }, (_, i) => i);
let arrayFilterCalls = 0;
const viaArray = nums
.filter((n) => {
arrayFilterCalls++;
return n % 2 === 0;
})
.map((n) => n * 2)
.slice(0, 3);
console.log(viaArray, "array filter 调用次数:", arrayFilterCalls); // 1000
let iterFilterCalls = 0;
const viaIter = nums
.values()
.filter((n) => {
iterFilterCalls++;
return n % 2 === 0;
})
.map((n) => n * 2)
.take(3)
.toArray();
console.log(viaIter, "iterator filter 调用次数:", iterFilterCalls); // 通常远小于 1000
六、重要行为与陷阱
- 一次性 :同一迭代器对象消费完后不能重来;需重新
arr.values()或重新创建管道。 - Iterator Helper 与底层共享进度 :对 Helper 的遍历会推动底层 迭代器(见 MDN 示例:
it与it.drop(0)共享数据源时,next会交错消耗)。 - 惰性副作用 :
map/filter里的回调在真正拉取对应项 时才执行;调试时随意console.log整个链可能不符合直觉。 reduce/toArray/forEach:往往意味着完整或少完整消费,与「只处理前几项」的目标搭配时要谨慎。- 异步迭代器 :
AsyncIterator另有asyncIterator协议 及for await...of;异步场景还有AsyncIterator.prototype.*等辅助(与同步 API 对称但返回 Promise)。流式、分页 API 在原文中作为典型用例,需用异步版本时请以当前引擎支持为准。
七、代码示例集(建议 Node 22+ 或新版浏览器)
以下示例可直接复制到控制台或保存为 .mjs 运行。同目录提供了汇总脚本 iterator-demos.mjs ,执行:node iterator-demos.mjs。
环境说明 :Iterator.from 以及 Iterator.prototype 上的 map / filter / take 等在 Node 22+ 中通常可用。Iterator.concat / Iterator.zip / Iterator.zipKeyed 属于联合迭代(Joint Iteration)相关能力,部分 Node 版本尚未内置 ;若 typeof Iterator.concat !== "function",需升级引擎、使用 polyfill,或用手写生成器达到同等效果。演示脚本在缺少静态方法时会回退到简单生成器示例,便于本地仍能跑通。
7.1 惰性管道:values → filter → map → take → toArray
javascript
const items = Array.from({ length: 10_000 }, (_, i) => ({
id: i,
active: i % 3 !== 0,
name: `user-${i}`,
}));
const names = items
.values()
.filter((u) => u.active)
.map((u) => u.name)
.take(5)
.toArray();
console.log(names);
7.2 drop + take(模拟 slice(start, start + count))
javascript
const arr = ["a", "b", "c", "d", "e", "f"];
const sliceLike = arr.values().drop(2).take(3).toArray();
console.log(sliceLike); // ["c", "d", "e"]
7.3 flatMap:每个元素展开为多段
javascript
const out = [1, 2, 3]
.values()
.flatMap((n) => [n, n * 10])
.toArray();
console.log(out); // [1, 10, 2, 20, 3, 30]
7.4 find / some / every(可提前结束)
javascript
const first = [2, 4, 5, 6].values().find((n) => n % 2 === 1);
console.log(first); // 5
const hasNegative = [1, 2, -3].values().some((n) => n < 0);
console.log(hasNegative); // true
const allPositive = [1, 2, 3].values().every((n) => n > 0);
console.log(allPositive); // true
7.5 无限序列 + find(不能先转数组)
javascript
function* fibonacci() {
let a = 1,
b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const firstThreeDigits = Iterator.from(fibonacci()).find((n) => n >= 100);
console.log(firstThreeDigits); // 144(或你环境中第一个 ≥100 的斐波那契数)
7.6 reduce(会消费整个迭代器)
javascript
const sum = [1, 2, 3, 4].values().reduce((acc, n) => acc + n, 0);
console.log(sum); // 10
7.7 Map.values() 上直接链式(不求和时避免先 [...map.values()])
javascript
const deposits = new Map([
["Ann", 100],
["Bob", 200],
]);
const total = deposits.values().reduce((a, b) => a + b, 0);
console.log(total); // 300
7.8 map / filter 回调的第二参数 index
javascript
const withIndex = ["x", "y", "z"]
.values()
.map((el, i) => `${i}:${el}`)
.toArray();
console.log(withIndex); // ["0:x", "1:y", "2:z"]
7.9 一次性:耗尽后需重新创建
javascript
const it = [1, 2, 3].values();
console.log([...it]); // [1, 2, 3]
console.log([...it]); // [] --- 已耗尽
const again = [1, 2, 3].values();
console.log([...again]); // [1, 2, 3]
7.10 Helper 与底层共享进度(交错 next 时)
javascript
const base = [1, 2, 3].values();
const helper = base.map((n) => n * 2);
console.log(base.next().value); // 1
console.log(helper.next().value); // 4 (来自底层已消费的第二个元素 2 的映射)
7.11 异步迭代器(惰性 take,示意)
若运行环境支持 AsyncIterator.prototype 上的辅助方法,可对异步生成器直接 take / filter 等,再在 async 函数里 await ... .toArray()(与参考文章中分页、流式场景一致):
javascript
async function* fetchIds() {
for (let i = 1; i <= 100; i++) {
await Promise.resolve(); // 模拟每步有异步
yield i;
}
}
async function demo() {
const firstThree = await fetchIds().take(3).toArray();
console.log(firstThree); // [1, 2, 3]
}
// demo();
(不同引擎对异步迭代器辅助方法的支持程度不一,以实际环境为准。)
八、参考链接
- Matt Smith --- Stop turning everything into arrays (and do less work instead)
- MDN:
Iterator(中文) - MDN:迭代协议(中文)
- ECMAScript® Language Specification --- %IteratorPrototype%
文档为学习笔记性质;具体参数名与边界情况以你使用的 JavaScript 引擎版本为准。