每当函数被调用时,JavaScript 引擎就会在函数顶部启动,并运行每行代码,直到到达底部。无法中途停止运行代码,并稍后重新开始。一直都是这种“运行到结束”的工作方式:
function getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
console.log(name);
}
console.log('the function has ended');
}
getEmployee();
运行上述代码将在控制台中输出以下内容:
the function has started
Amanda
Diego
Farrin
James
Kagure
Kavita
Orit
Richard
the function has ended
如果你想先输出前三名员工的姓名,然后停止一段时间,稍后再从停下的地方继续输出更多员工的姓名呢?普通函数无法这么做,因为无法中途“暂停”运行函数。
可暂停的函数
如果我们希望能够中途暂停运行函数,则需要使用 ES6 中新提供的一种函数,叫做 generator(生成器)函数!我们来看一个示例:
function* getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
console.log( name );
}
console.log('the function has ended');
}
注意到 function
关键字后面的星号(即 *
)了吗?星号表示该函数实际上是生成器!
生成器的星号实际上可以放在 function
关键字和函数名称之间的任何位置。因此三个都是有效的生成器声明!
ES6 社区基本同意将星号放在 function
关键字之后(即 function* 名称() { … }
)。但是,其他人建议将星号放在函数名称前面。因此请务必注意,星号表示函数是生成器,但是星号的位置并不重要
现在看看当我们尝试运行该函数时,会发生什么:
getEmployee();
// 这是我在 Chrome 中获得的回应:
getEmployee {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
生成器和迭代器
生成器被调用时,它不会运行函数中的任何代码,而是创建和返回迭代器。该迭代器可以用来运行实际生成器的内部代码。
const generatorIterator = getEmployee();
generatorIterator.next();
产生我们期望的代码:
the function has started
Amanda
Diego
Farrin
James
Kagure
Kavita
Orit
Richard
the function has ended
如果你自己尝试运行这段代码,迭代器的 .next()
方法第一次被调用时,它会运行生成器中的所有代码。注意到什么了吗?代码始终没有暂停!那么我们要怎么才能实现神奇的暂停功能呢?
关键字 yield
关键字 yield
是 ES6 中新出现的关键字。只能用在生成器函数中。yield
会导致生成器暂停下来。我们向我们的生成器中添加 yield
,试试看:
function* getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
console.log(name);
yield;
}
console.log('the function has ended');
}
注意,现在 for...of
循环中出现了 yield
。如果我们调用该生成器(生成迭代器),然后调用 .next()
,将获得以下输出:
const generatorIterator = getEmployee();
generatorIterator.next();
将输出以下内容到控制台:
the function has started
Amanda
暂停了!但是要真的确定下,我们看看下次迭代:
generatorIterator.next();
将以下内容输出到控制台:
Diego
它能完全记住上次停下的地方!它获取到数组中的下一项(Diego),记录它,然后再次触发了 yield
,再次暂停。
现在能够很好的暂停了,但是如果将数据从生成器返回到外面的世界呢?我们可以使用 yield
实现这一点。
向外面的世界生成数据
我们不再向控制台输出姓名并暂停,而是让代码返回姓名并暂停。
function* getEmployee() {
console.log('the function has started');
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
for (const name of names) {
yield name;
}
console.log('the function has ended');
}
注意,现在从 console.log(name);
切换成了 yield name;
。做出这一更改后,当生成器运行时,它会把姓名从函数里返回出去,然后暂停执行代码。我们看看具体效果:
const generatorIterator = getEmployee();
let result = generatorIterator.next();
result.value // 是 "Amanda"
generatorIterator.next().value // 是 "Diego"
generatorIterator.next().value // 是 "Farrin"
迭代器的 .next()
方法需要被调用。被调用的次数将比生成器函数中的 yield
表达式的数量多一次,才能完全完成/用尽下面的 udacity
生成器函数
我们可以使用关键字 yield 从生成器中获取数据。我们还可以将数据发送回生成器中。方式是使用 .next()
方法:
function* displayResponse() {
const response = yield;
console.log(`Your response is "${response}"!`);
}
const iterator = displayResponse();
iterator.next(); // 开始运行生成器函数
iterator.next('Hello Udacity Student'); // 将数据发送到生成器中
// 上面的一行打印到控制台:你的响应是 "Hello Udacity Student"!
使用数据调用 .next()
(即 .next('Richard')
)会将该数据发送到生成器函数中上次离开的地方。它会将 yield 关键字替换为你提供的数据。
关键字 yield
用来暂停生成器并向生成器外发送数据,然后 .next()
方法用来向生成器中传入数据。下面是使用这两种过程来一次次地循环访问姓名列表的示例:
function* getEmployee() {
const names = ['Amanda', 'Diego', 'Farrin', 'James', 'Kagure', 'Kavita', 'Orit', 'Richard'];
const facts = [];
for (const name of names) {
// yield *出* 每个名称并将返回的数据存储到 facts 数组中
facts.push(yield name);
}
return facts;
}
const generatorIterator = getEmployee();
// 从生成器中获取第一个名称
let name = generatorIterator.next().value;
// 将数据传入 *并* 获取下一个名称
name = generatorIterator.next(`${name} is cool!`).value;
// 将数据传入 *并* 获取下一个名称
name = generatorIterator.next(`${name} is awesome!`).value;
// 将数据传入 *并* 获取下一个名称
name = generatorIterator.next(`${name} is stupendous!`).value;
// 你懂的
name = generatorIterator.next(`${name} is rad!`).value;
name = generatorIterator.next(`${name} is impressive!`).value;
name = generatorIterator.next(`${name} is stunning!`).value;
name = generatorIterator.next(`${name} is awe-inspiring!`).value;
// 传递最后一个数据,生成器结束并返回数组
const positions = generatorIterator.next(`${name} is magnificent!`).value;
// 在自己的行上显示每个名称及其描述
positions.join('\n');
生成器是强大的新型函数,能够暂停执行代码,同时保持自己的状态。生成器适用于一次一个地循环访问列表项,以便单独处理每项,然后再转到下一项。还可以使用迭代器来处理嵌套回调。例如,假设某个函数需要获得所有仓库的列表和被加星标的次数。在获得每个仓库的星标数量之前,需要获得用户的信息。获得用户的个人资料后,代码可以利用该信息查找所有的仓库。