ES6+ Generator 基础
1. 前言
上节我们学习了 ES6 中 迭代 的相关内容,并实现了一个迭代器。我们知道实现一个迭代器,我们需要手动添加对象的 Symbol.iterator
属性,并需要实现 next 方法。那么有没有什么可以帮助我们自动实现迭代器呢?ES6 给出了生成器的方法来满足我们的需求。我们不需要在对象上添加 Symbol.iterator
属性,使用生成器函数就可以实现迭代器的功能。本节我们将学习生成器的相关概念和基础用法。
有些概念是我们必须要理解的,前面在学习迭代器的时候,我们学习了迭代协议和迭代器协议,实现一个迭代器需要满足这两个协议才算是一个真正的迭代器。而本节的生成器和生成器函数也是如此,我们也需要知道生成器对象和生成器函数概念和它们直接的关系。
Generator 就是我们说的生成器,它包含两个概念 生成器对象和生成器函数
。首先,要理解的是生成器对象和迭代器的关系,生成器对象是遵守迭代协议和迭代器协议实现的 Iterable 接口,可以理解生成器对象其实也是一个迭代器;然后,我们需要理解什么是生成器函数,生成器函数是由 function *
来定义的,并且返回结果是一个 Generator 对象。
function* generator() {
yield 'a';
yield 'b';
}
var gen = generator(); // Object [Generator] {}
2.1 Generator.prototype.next()
使用 yield 返回的值会被迭代器的 next () 方法捕获:
var gen = generator();
gen.next() // {value: 'a', done: false}
gen.next() // {value: 'b', done: false}
gen.next() // {value: undefined, done: true}
从上面代码的执行结果可以看出,生成器函数在执行后会返回一个生成器对象,这个生成器对象满足迭代协议和迭代器协议,所以我们可以去手动调用它的 next () 方法去获取每一步的返回值。从这里可以看出,生成器其实就是迭代器的一个应用,并且这个应用会在异步中大放异彩。
2.2 Generator.prototype.return()
var gen = generator();
gen.next(); // { value: 'a', done: false }
gen.return("imooc"); // { value: "imooc", done: true }
gen.next(); // { value: undefined, done: true }
var gen = generator();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: undefined, done: true }
gen.return(); // { value: undefined, done: true }
gen.return(); // { value: 1, done: true }
2.2 Generator.prototype.throw()
function* generator() {
while(true) {
try {
yield 'imooc'
} catch(e) {
console.log("Error caught!");
}
}
}
var gen = generator();
gen.next(); // { value: "imooc", done: false }
gen.throw(new Error("error")); // "Error caught!"
3. Generator 案例
3.1 类数组转化
function fn() {
const arg = [...arguments];
console.log(arg);
}
fn(, , ); // [1, 2, 3]
当然我们知道类数组的定义,所以我们自己定义一个类数组,看能不能使用展开运算符将类数组转化为数组:
const likeArr = {
: ,
: ,
length: ,
}
console.log([...likeArr]); // Uncaught TypeError: likeArr is not iterable
上面代码中我们定义了一个类数组,但是使用展开运算符报错了,提示我们 likeArr 不是一个迭代器。因为在函数中类数组是内部帮我们实现了迭代器的功能,而我们自己定义的类数组是不具有迭代器功能的,那我们来自己实现一个:
likeArr[Symbol.iterator] = function() {
let index = ;
return {
next: () => {
return { value: this[index], done: index++ === this.length}
}
}
}
console.log([...likeArr]); // [1, 2]
上面的代码我们在 likeArr 对象上定义了 Symbol.iterator
它具有迭代功能。上面代码中我们需要手动地去实现 next () 方法,这比较麻烦,那能不能简化一下呢?我们的生成器函数就出场了:
likeArr[Symbol.iterator] = function* () {
let index = ;
while (index !== this.length) {
yield this[index++];
}
}
console.log([...likeArr]); // [1, 2]
3.2 单步获取质数
还有一个案例是面试中经常会考到的:
function isPrime(num) {
for (let i = ; i <= Math.sqrt(num); i++) {
if (num % i === ) {
return false
}
}
return true
}
function primeHandler() {
let prime =
return () => {
while (true) {
prime++
if (isPrime(prime)) {
return prime
}
}
}
}
const getPrime = primeHandler()
console.log(getPrime()); // 2
console.log(getPrime()); // 3
console.log(getPrime()); // 5
既然是单步执行的,那么我们就可以使用迭代器方式实现:
var prime = {}
prime[Symbol.iterator] = function() {
let prime = ;
return {
next() {
while(true) {
prime++
if (isPrime(prime)) {
return prime;
}
}
}
}
}
var getPrime = prime[Symbol.iterator]().next;
console.log(getPrime()); // 2
console.log(getPrime()); // 3
function* primeGenerator () {
let prime =
while (true) {
prime++
if (isPrime(prime)) {
yield prime
}
}
}
var getPrime = primeGenerator().next().value
console.log(getPrime()); // 2
console.log(getPrime()); // 3