解释 JavaScript 中不同类型的生成器

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 代码。