1. 生成器的概述
在 JavaScript 中,生成器是一种可以暂停与恢复执行的函数。在函数中,可以使用 yield
关键字暂停执行并返回一个值,而使用 next()
方法可以恢复执行。生成器可以用于异步代码中,或者在需要按需加载数据时进行迭代处理。
2. 简单生成器
2.1 创建简单生成器
以下是一个简单的生成器函数的例子:
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const generator = simpleGenerator();
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.next()); // {value: 2, done: false}
console.log(generator.next()); // {value: 3, done: false}
console.log(generator.next()); // {value: undefined, done: true}
在上面的例子中,我们创建了一个简单的生成器函数 simpleGenerator()
, 包含三个 yield
语句,分别返回值 1、2、3。我们通过调用 next()
方法来从生成器中取得这些值。
2.2 生成器的执行
生成器函数会返回一个生成器对象。可以使用 next()
方法从生成器中获取值。在调用 next()
方法时,生成器中的代码会执行直到遇到 yield
语句。
下面是代码示例:
function* simpleGenerator() {
console.log('Start');
yield 1;
console.log('Middle');
yield 2;
console.log('End');
}
const generator = simpleGenerator();
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());
运行结果会显示以下内容:
Start
{value: 1, done: false}
Middle
{value: 2, done: false}
End
{value: undefined, done: true}
从代码执行顺序可以看出,当调用 generator.next()
方法时,会执行 simpleGenerator()
函数中的代码,直到遇到 yield
语句为止。每当执行到 yield
语句时,代码会暂停并返回值。
2.3 迭代器与生成器
生成器函数返回的生成器对象同时也是一个迭代器。迭代器是一种对象,它实现了 next()
方法,该方法返回类似于以下结构的对象:
{
value: /* 值 */,
done: /* 是否完成此迭代 */,
}
下面是一个使用迭代器遍历数组元素的例子:
const arr = [1, 2, 3];
const iterator = arr[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}
在这个例子中,我们首先通过数组的 Symbol.iterator
属性获取到一个迭代器对象,然后每次调用 next()
方法获取数组的下一个值。
3. 调用生成器函数时传参
在生成器函数中,可以使用函数的参数来暂停和恢复执行。下面是一个例子,演示生成器函数如何接收参数:
function* paramGenerator(x){
const y = 2 * (yield (x + 1));
const z = yield (y / 3);
return (x + y + z);
}
const generator = paramGenerator(5);
console.log(generator.next()); // {value: 6, done: false}
console.log(generator.next(12)); // {value: 8, done: false}
console.log(generator.next(13)); // {value: 42, done: true}
在上面的例子中,我们调用 paramGenerator()
函数时传入参数 5。第一次调用 generator.next()
方法时,代码会执行至第一个 yield
语句处,然后返回 (5 + 1)
,即值 6。
第二次调用 generator.next()
方法时,相当于向生成器“发送”了一个值 12,它会暂停代码的执行,并将值 12 存储在变量 y
中。然后代码执行至第二个 yield
语句,返回结果为 (2 * 12) / 3
,即值 8。
最后一次调用 generator.next()
时并没有向生成器发送任何值,代码会执行至函数的末尾并返回 (5 + 24 + 13) = 42
。
4. 生成器的应用场景
4.1 异步执行
使用生成器可以更方便地编写异步执行代码。下面是一个例子,使用生成器封装了一个使用 XMLHttpRequest
对象发送 HTTP 请求的函数:
function request(url) {
return new Promise(function(resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState === XMLHttpRequest.DONE) {
if(xhr.status === 200) {
resolve(xhr.response);
}else {
reject(xhr.statusText);
}
}
};
xhr.open('GET', url, true);
xhr.send();
});
}
function* makeRequest() {
const result = yield request('https://jsonplaceholder.typicode.com/todos/1');
const json = JSON.parse(result);
const title = json.title;
const userId = json.userId;
const userResult = yield request(`https://jsonplaceholder.typicode.com/users/${userId}`);
const userData = JSON.parse(userResult);
const email = userData.email;
return {
title: title,
email: email,
}
}
const generator = makeRequest();
generator.next()
.then(response => generator.next(response))
.then(response => generator.next(response))
.then(result => console.log(result));
在这个例子中,我们首先封装了一个异步请求函数 request()
,然后使用生成器编写了一个 makeRequest()
函数,该函数使用 request()
发送两个 HTTP 请求来获取 JSON 数据,并从中获取信息。在主函数中,我们通过使用 Promise 和 then()
方法来逐步从生成器中获取并处理数据。
4.2 惰性生成器
在生成器中使用 yield
关键字可以实现按需加载数据的逻辑,在处理大量数据时,能够显著优化性能。下面是一个惰性生成器的例子:
function* lazyGenerator() {
let i = 0;
while(true) {
yield i++;
}
}
const generator = lazyGenerator();
console.log(generator.next()); // {value: 0, done: false}
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.next()); // {value: 2, done: false}
console.log(generator.next()); // {value: 3, done: false}
在这个例子中,我们定义了一个无限制的生成器 lazyGenerator()
,每次调用 next()
方法返回的值都会增加。这种生成器可以用于处理一些需要按需加载数据的场景,比如分页加载。
4.3 生成器与迭代器的组合
在 JavaScript 中,生成器函数可以与迭代器(iterator)和可迭代对象(iterable)联合使用,以提供更加强大的语言特性。下面是一个例子,展示了如何使用生成器函数和自定义迭代器来遍历数据集合:
const myCollection = {
items: [],
*[Symbol.iterator]() {
yield* this.items;
}
}
myCollection.items.push(1);
myCollection.items.push(2);
myCollection.items.push(3);
for(const item of myCollection) {
console.log(item);
}
在这个例子中,我们定义了一个数据集合对象 myCollection
,它包含一个数组和一个使用 Symbol.iterator
属性定义的自定义迭代器。迭代器函数会通过 yield*
语句将数据迭代出来。然后我们通过 for..of
循环遍历了整个数据集。
5. 结论
JavaScript 的生成器是一种可以暂停与恢复执行的函数,在异步处理或按需加载数据时非常有用。通过使用生成器函数和迭代器,我们可以编写出更加强大的、灵活的 JavaScript 代码。