各位朋友,大家好!我是你们的老朋友,今天咱们不聊八卦,只聊聊 JavaScript 里那些有点神秘,但又非常实用的东西:Iterator
(迭代器)和 Generator
(生成器)。它们就像 JavaScript 世界里的“懒人神器”,能帮助我们实现惰性求值和流式处理,让代码跑得更高效,更优雅。
开场白:告别“一口吃个胖子”的时代
想象一下,你要处理一个巨大的数组,比如一个包含 100 万条数据的日志文件。如果你一口气把所有数据加载到内存里,然后进行处理,那你的电脑可能会直接罢工。这就是典型的“一口吃个胖子”的做法,效率低,而且容易造成内存溢出。
但是,如果我们能像吃面条一样,每次只吃一小口,吃完一口再吃下一口,那问题就迎刃而解了。Iterator
和 Generator
就是帮助我们实现这种“分批处理”的关键。
第一幕:Iterator
——遍历的幕后英雄
Iterator
是一种接口,它为不同的数据结构提供了一种统一的访问机制。简单来说,它定义了一种方法,让你能够按顺序访问集合中的每一个元素,而无需了解集合内部的实现细节。
-
Iterator
协议的核心:next()
方法Iterator
协议的核心在于next()
方法。每次调用next()
方法,它都会返回一个对象,这个对象包含两个属性:value
: 集合中的下一个值。done
: 一个布尔值,表示是否已经遍历完整个集合。true
表示已经遍历结束,false
表示还有更多元素。
举个例子,我们手动创建一个简单的
Iterator
:const myIterator = { data: [1, 2, 3], index: 0, next: function() { if (this.index < this.data.length) { return { value: this.data[this.index++], done: false }; } else { return { value: undefined, done: true }; } } }; console.log(myIterator.next()); // { value: 1, done: false } console.log(myIterator.next()); // { value: 2, done: false } console.log(myIterator.next()); // { value: 3, done: false } console.log(myIterator.next()); // { value: undefined, done: true }
-
原生支持
Iterator
的数据结构在 JavaScript 中,一些内置的数据结构已经实现了
Iterator
接口,包括:Array
Map
Set
String
TypedArray
arguments
对象NodeList
(DOM 节点列表)
这意味着我们可以直接使用
for...of
循环来遍历这些数据结构:const myArray = [10, 20, 30]; for (const value of myArray) { console.log(value); // 10, 20, 30 } const myString = "Hello"; for (const char of myString) { console.log(char); // H, e, l, l, o }
-
自定义
Iterator
如果你想让自己的对象也能够使用
for...of
循环,你需要实现Iterator
接口。具体来说,你需要为你的对象添加一个Symbol.iterator
属性,这个属性的值是一个函数,这个函数返回一个Iterator
对象。const myObject = { data: ['a', 'b', 'c'], [Symbol.iterator]: function() { let index = 0; const data = this.data; return { next: function() { if (index < data.length) { return { value: data[index++], done: false }; } else { return { value: undefined, done: true }; } } }; } }; for (const value of myObject) { console.log(value); // a, b, c }
或者更优雅一点,使用箭头函数:
const myObject = { data: ['x', 'y', 'z'], [Symbol.iterator]: () => { let index = 0; const data = this.data; // 注意这里要访问外部的data return { next: () => { // 这里也要用箭头函数,否则this指向错误 if (index < data.length) { return { value: data[index++], done: false }; } else { return { value: undefined, done: true }; } } }; } }; for (const value of myObject) { console.log(value); // x, y, z }
注意: 在箭头函数中,
this
的指向是固定的,它指向的是定义时所在的对象,而不是运行时所在的对象。 因此,在上面的例子中,内部next()
函数也必须是箭头函数,否则this.data
将会是undefined
。 另外,因为Symbol.iterator
的箭头函数定义时,this
指向的是全局对象 (window 或 undefined 在严格模式下),所以需要在外层Symbol.iterator
函数中捕获this.data
。
第二幕:Generator
——迭代器的进化版
Generator
是 JavaScript 中一种特殊的函数,它可以让你暂停和恢复函数的执行。它可以生成一个 Iterator
对象,从而实现惰性求值。
-
Generator
函数的定义Generator
函数的定义和普通函数类似,只是在function
关键字后面加一个星号*
。function* myGenerator() { yield 1; yield 2; yield 3; }
-
yield
关键字yield
关键字是Generator
函数的核心。它用于暂停函数的执行,并将yield
后面的表达式的值作为Iterator
的value
属性返回。 -
创建
Iterator
对象调用
Generator
函数并不会立即执行函数体内的代码,而是会返回一个Iterator
对象。const iterator = myGenerator(); 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 }
-
Generator
的优点:惰性求值Generator
的最大优点在于它的惰性求值特性。只有当你调用next()
方法时,它才会执行函数体内的代码,并计算出下一个值。这意味着你可以生成一个无限序列,而不用担心内存溢出。function* infiniteSequence() { let num = 0; while (true) { yield num++; } } const sequence = infiniteSequence(); console.log(sequence.next().value); // 0 console.log(sequence.next().value); // 1 console.log(sequence.next().value); // 2 // ... 可以无限生成下去
-
*
Generator
的高级用法:`yield`**yield*
关键字可以用于委托给另一个Iterator
或Generator
。它可以将一个Iterator
或Generator
生成的值依次yield
出来。function* anotherGenerator() { yield 'a'; yield 'b'; } function* mainGenerator() { yield 1; yield* anotherGenerator(); yield 2; } const iterator = mainGenerator(); console.log(iterator.next()); // { value: 1, done: false } console.log(iterator.next()); // { value: 'a', done: false } console.log(iterator.next()); // { value: 'b', done: false } console.log(iterator.next()); // { value: 2, done: false } console.log(iterator.next()); // { value: undefined, done: true }
-
Generator
的高级用法:next()
方法的参数next()
方法可以接受一个参数,这个参数会作为上一个yield
表达式的返回值。function* myGenerator() { const input = yield '等待输入...'; console.log('输入的值是:', input); } const iterator = myGenerator(); console.log(iterator.next()); // { value: '等待输入...', done: false } console.log(iterator.next('Hello World')); // 输入的值是: Hello World // { value: undefined, done: true }
-
Generator
的高级用法:return()
和throw()
方法-
return(value)
方法:强制结束Generator
函数的执行,并返回一个带有指定value
属性和done: true
属性的对象。 -
throw(error)
方法:在Generator
函数内部抛出一个错误。
这两个方法通常用于处理异常情况。
-
第三幕:实战演练——惰性求值和流式处理
现在,让我们通过一些实战例子来展示 Iterator
和 Generator
在惰性求值和流式处理中的应用。
-
示例 1:处理大型日志文件
假设我们有一个大型的日志文件,我们需要统计其中包含特定关键词的行数。我们可以使用
Generator
来逐行读取文件,并进行处理,而无需一次性将整个文件加载到内存中。function* readLines(filePath) { // 模拟读取文件 const fileContent = ` This is line 1 with keyword: error This is line 2. This is line 3 with keyword: warning This is line 4. This is line 5 with keyword: error `; const lines = fileContent.trim().split('n'); for (const line of lines) { yield line; } } function countKeyword(filePath, keyword) { let count = 0; for (const line of readLines(filePath)) { if (line.includes(keyword)) { count++; } } return count; } const filePath = 'path/to/your/log/file.txt'; const keyword = 'error'; const errorCount = countKeyword(filePath, keyword); console.log(`包含关键词 "${keyword}" 的行数: ${errorCount}`); // 包含关键词 "error" 的行数: 2
-
示例 2:生成斐波那契数列
斐波那契数列是一个无限序列,我们可以使用
Generator
来按需生成数列中的元素。function* fibonacci() { let a = 0; let b = 1; while (true) { yield a; [a, b] = [b, a + b]; } } const fib = fibonacci(); console.log(fib.next().value); // 0 console.log(fib.next().value); // 1 console.log(fib.next().value); // 1 console.log(fib.next().value); // 2 console.log(fib.next().value); // 3 console.log(fib.next().value); // 5 // ... 可以无限生成下去
-
示例 3:无限数据流的处理
假设你正在处理一个来自传感器的数据流,你需要对这些数据进行实时分析。你可以使用
Generator
来创建一个数据流处理管道,对数据进行过滤、转换和聚合,而无需将整个数据流存储在内存中。function* sensorDataStream() { // 模拟传感器数据 let i = 0; while (true) { yield Math.random() * 100; // 0-100 的随机数 i++; if (i > 10) return; // 模拟有限的数据 } } function* filterData(dataStream, threshold) { for (const data of dataStream) { if (data > threshold) { yield data; } } } function* transformData(dataStream, transformFn) { for (const data of dataStream) { yield transformFn(data); } } const dataStream = sensorDataStream(); const filteredStream = filterData(dataStream, 50); // 过滤掉小于 50 的数据 const transformedStream = transformData(filteredStream, data => data.toFixed(2)); // 保留两位小数 for (const data of transformedStream) { console.log(data); // 输出过滤和转换后的数据 }
第四幕:Async Generator
——异步迭代的利器
在异步编程中,我们经常需要处理异步数据流。Async Generator
是一种特殊的 Generator
函数,它可以让你在 yield
表达式中使用 await
关键字,从而实现异步迭代。
-
Async Generator
函数的定义Async Generator
函数的定义和普通Generator
函数类似,只是在function
关键字前面加一个async
关键字。async function* myAsyncGenerator() { yield await Promise.resolve(1); yield await Promise.resolve(2); yield await Promise.resolve(3); }
-
使用
for await...of
循环你可以使用
for await...of
循环来遍历Async Generator
生成的Async Iterator
。async function processData() { const asyncIterator = myAsyncGenerator(); for await (const value of asyncIterator) { console.log(value); // 1, 2, 3 } } processData();
-
示例:异步数据流的处理
async function* fetchUsers() { const userIds = [1, 2, 3]; for (const userId of userIds) { const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`); const user = await response.json(); yield user; } } async function displayUsers() { for await (const user of fetchUsers()) { console.log(user.name); // 输出用户名 } } displayUsers();
总结:Iterator
和 Generator
的魅力
Iterator
和 Generator
是 JavaScript 中非常强大的工具,它们可以帮助你:
- 实现惰性求值: 只在需要的时候才计算值,避免浪费资源。
- 处理大型数据集: 分批处理数据,避免内存溢出。
- 创建数据流处理管道: 对数据进行实时分析和转换。
- 简化异步编程: 使用
Async Generator
处理异步数据流。
掌握 Iterator
和 Generator
,可以让你写出更高效、更优雅的 JavaScript 代码。它们就像编程界的“瑞士军刀”,功能强大,用途广泛,绝对值得你深入学习和掌握。
表格:Iterator
和 Generator
的对比
特性 | Iterator |
Generator |
---|---|---|
定义 | 接口,定义了 next() 方法 |
函数,使用 function* 定义,包含 yield 关键字 |
作用 | 提供统一的遍历机制 | 生成 Iterator 对象,实现惰性求值 |
创建方式 | 手动实现 next() 方法,实现 Symbol.iterator |
调用 Generator 函数,返回 Iterator 对象 |
核心关键字 | next() |
yield |
惰性求值 | 不支持 | 支持 |
异步迭代 | 不支持 | 支持 Async Generator ,使用 async function* 和 await 关键字 |
适用场景 | 需要自定义遍历逻辑的数据结构 | 需要惰性求值、处理大型数据集、创建数据流处理管道的场景 |
示例数据结构 | 自定义对象 | 无限序列、大型日志文件、传感器数据流 |
结束语:一起成为更优秀的 JavaScript 开发者
希望今天的分享能够帮助大家更好地理解 Iterator
和 Generator
。 编程之路漫漫,让我们一起努力,不断学习,不断进步,成为更优秀的 JavaScript 开发者! 如果大家有什么问题,欢迎随时提问,我会尽力解答。感谢大家的聆听!