ES6+ Generator 基础
前面我们花了三节深入地学习了 ES6 的异步案 Promise,本节学习的器也是为了异步而生的,但是它的出发思路和 Promise 截然不同。@H__10@
上节我们学习了 ES6 中 的相关,并实现了迭代器。我们知道实现迭代器,我们需要手动对象的 Symbol.iterator
,并需要实现 next 。那么有没有什么可以帮助我们实现迭代器呢?ES6 给出了器的来满足我们的需求。我们不需要在对象上 Symbol.iterator
,使用器就可以实现迭代器的。本节我们将学习器的相关概念和基础。@H__10@
器是灵活的结构,能使得块内部暂停和恢复执行的能力。在实际应用中,使用器可以迭代器和协程。@H__10@
有些概念是我们必须要理解的,前面在学习迭代器的时候,我们学习了迭代协议和迭代器协议,实现迭代器需要满足这两个协议才算是真正的迭代器。而本节的器和器也是如此,我们也需要知道器对象和器概念和它们直接的关系。@H__10@
Generator 就是我们说的器,它包含两个概念 器对象和器
。首先,要理解的是器对象和迭代器的关系,器对象是遵守迭代协议和迭代器协议实现的 Iterable 接口,可以理解器对象其实也是迭代器;然后,我们需要理解什么是器,器是由 function *
来定义的,并且返回结果是 Generator 对象。@H__10@
器是特殊的,在后会返回器对象,这个器对象是遵守可迭代协议和迭代器协议实现的 Iterable 接口。器可以使用 yield 关键字来暂停执行的器:@H__10@
function* generator() {
yield 'a';
yield 'b';
}
var gen = generator(); // Object [Generator] {}
器的 next () 和迭代器返回的结果是一样的,返回了包含 done
和 value
的对象,该也可以通过接受参数用以向器传值。@H__10@
使用 yield 返回的值会被迭代器的 next () 捕获:@H__10@
var gen = generator();
gen.next() // {value: 'a', done: false}
gen.next() // {value: 'b', done: false}
gen.next() // {value: undefined, done: true}
从上面的执行结果可以看出,器在执行后会返回器对象,这个器对象满足迭代协议和迭代器协议,所以我们可以去手动它的 next () 去每一步的返回值。从这里可以看出,器其实就是迭代器的应用,并且这个应用会在异步中大放异彩。@H__10@
return()
返回给定的值并结束器。@H__10@
var gen = generator();
gen.next(); // { value: 'a', done: false }
gen.return("imooc"); // { value: "imooc", done: true }
gen.next(); // { value: undefined, done: true }
另外,如果对已经完成状态的器 return(value)
则器会一直保持在完成状态,如果出入参数,value
会设置成传入的参数,done
的值不变:@H__10@
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 }
throw()
用来向器抛出异常,并恢复器的执行,返回带有 done
及 value
两个的对象。@H__10@
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 @H_934_@Error("error")); // "Error caught!"
将类数组转化为真正的数组方式有很多,ES6 提供了 Array.from()
可以将类数组转化为数组 。另外在一些中可以使用 [...argument]
的方式转化类数组。@H__10@
function fn() {
const arg = [...arguments];
console.log(arg);
}
fn(, , ); // [1, 2, 3]
当然我们知道类数组的定义,所以我们自己定义类数组,看能不能使用展开运算符将类数组转化为数组:@H__10@
const likeArr = {
: ,
: ,
length: ,
}
console.log([...likeArr]); // Uncaught TypeError: likeArr is not iterable
上面中我们定义了类数组,但是使用展开运算符报错了,我们 likeArr 不是迭代器。因为在中类数组是内部帮我们实现了迭代器的,而我们自己定义的类数组是不具有迭代器的,那我们来自己实现:@H__10@
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 () ,这比较麻烦,那能不能简化一下呢?我们的器就出场了:@H__10@
likeArr[Symbol.iterator] = function* () {
let index = ;
while (index !== this.length) {
yield this[index++];
}
}
console.log([...likeArr]); // [1, 2]
上面的使用了器,并且没有去手动实现 next () ,从这里我们也能很清楚地知道迭代器和器的关系。而且使用器更加简单方便。@H__10@
还有案例是面试中经常会考到的:@H__10@
题目:实现,每次下质数,要求不使用,且本身不接受任何参数@H__10@
从题目的要求可以知道,这个每次都会返回质数,也就是说每次后都会返回。@H__10@
首先我们定义判断数是否为质数的:@H__10@
function isPrime(num) {
for (let i = ; i <= Math.sqrt(num); i++) {
if (num % i === ) {
return false
}
}
return true
}
传统的方式是使用闭包来:@H__10@
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
既然是单步执行的,那么我们就可以使用迭代器方式实现:@H__10@
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
上实例我们知道实现迭代器的方式是很麻烦的,可以使用器去替代迭代器的,所以上面的可以使用器改造如下:@H__10@
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
本节我们主要学习了器的概念和,需要器对象是由器返回的结果,器对象是遵守迭代协议和迭代器协议实现的 Iterable 接口。器其实就是对迭代器的应用。另外,通过两个案例更加深刻地理解了器的应用场景,对比了器和迭代器的不同。@H__10@