Generator函数是ES6提供的一种异步编程解决方案,它语法行为与传统函数不一样,咱们先来看一个使用Generator书写的Fibonacci函数的示例:es6
function* fibonacci(n) {
let current = 0;
let next = 1;
while (n-- > 0) {
yield current;
[current, next] = [next, next + current];
}
}
复制代码
费波那契数列由0和1开始,以后的费波那契系数就是由以前的两数相加而得出。而咱们上面的生成器参数n即表明须要计算到第几个序列:编程
const test = fibonacci(3);
console.log(test.next()); // {value: 0, done: false}
console.log(test.next()); // {value: 1, done: false}
console.log(test.next()); // {value: 1, done: false}
console.log(test.next()); // {value: undefined, done: true}
for (item of fibonacci(3)) {
console.log(item) // 0, 1, 1
}
复制代码
经过以上实践,能够简单总结出生成器的几个特色:数组
{value: any, done: boolean}
。for ... of
执行。Generator生成器与普通函数相比具备2个特征:bash
*
。yield
表达式。当在调用生成器时,它不容许使用new关键字,也不会当即执行,而是返回一个Iterator
对象,容许经过遍历器、for...of
、解构语法等表达式执行。数据结构
每个 yield 表达式,会使 Generator 生成一种新的状态,并转交控制权给外部函数,此时咱们须要调用遍历器对象的next
方法,才能使 Generator 继续执行。须要注意的是,Generator函数内部若是不存在yield
表达式,它也不会当即执行,而是须要手动使用next
方法触发:异步
function* test() { console.log('hello') }
const fn = test(); // 没有任何输出
fn.next(); // hello
复制代码
Generator函数结束的标志为 return,return 返回的值也会做为next方法返回对象的value,而此时 done 属性为:true(若是函数体内无 return 关键字,则会执行到函数结束,默认返回值为undefined)。异步编程
yield表达式用于定义Generator不一样的内部状态,它同时做为函数暂停的标志,将执行权交给外部的其余函数,并将 yield 关键字紧邻的表达式做为接下来遍历器的next()
方法返回的对象的value键值。外部函数在调用了next()
方法之后,Generator才得以恢复执行:函数
function* test() {
yield 'hello'
yield 'world'
return '!'
}
const executer = test();
executer.next(); // {value: "hello", done: false}
executer.next(); // {value: "world", done: false}
executer.next(); // {value: "!", done: true}
for (item of test()) { console.log(item) } // hello world
复制代码
return与yield都能使函数中止执行,并将后面的表达式的值做为返回对象value传递出去,区别在于return是函数结束的标识,不具有屡次执行的能力,返回的值也不能做为迭代对象使用(迭代器若是判断 done 标识为true,则会忽略该值)。学习
那咱们能够在生成器中调用生成器吗?最开始尝试时可能会这样写:ui
function* hello() {
yield 'hello'
yield world()
}
function* world() {
yield 'world'
}
for (item of hello()) { console.log(item) }
// hello
// world {<suspended>}
复制代码
第二个迭代器的值返回的是一个新的Generator,它按照原样返回了,并无按照咱们预想中执行。为了在一个Generator函数里执行另外一个Generator,此时就须要使用yield*
表达式:
function* hello() {
yield 'hello'
yield* world()
}
function* world() {
yield 'world'
}
for (item of hello()) { console.log(item) }
// hello
// world
复制代码
yield*
语句后面能接生成器对象或是实现了Iterator接口的值(字符串对象、数组对象等),它的做用就像是将生成器对象进行了for...of
遍历,将每个遍历到的对象传递到当前的生成器中执行yield,举一个示例:
function* example() {
yield 'hello';
yield* ['world'];
yield* test();
yield* '??';
}
function* test() {
yield '!';
}
// example函数等同于
function* example() {
yield 'hello';
for (item of ['world']) {
yield item;
}
yield '!'
for (item of '??') {
yield item;
}
}
复制代码
经过以上对Generator函数的介绍,咱们对Iterator有了一个初步的了解,Generator函数在运行后,会生成一个遍历器对象,再由for...of
语法或是解构函数对Iterator进行消费。其实Iterator不只仅应用于Generator,它其实仍是一种通用的接口规范,为不一样的数据结构提供统一的访问机制。
ES6规定,Iterator接口部署在对象的Symbol.iterator
上,凡是实现了这一属性的对象都认为是可遍历的(iterable),原生具有Iterator接口的数据结构有:
所以对一个字符串来说,咱们能够手动获取到它的遍历器对象,并进行循环打印每个字符:
const strs = 'hello world'
for (str of strs[Symbol.iterator]()) { console.log(str) }
复制代码
除以上原生实现了Iterable数据结构之外,咱们还能够本身定义任意对象的Symbol.iterator
属性方法,从而实现Iterable特性,该属性方法具有如下特征:
Iterator Object
Iterator Object
中,存在一个next()
方法,该函数老是返回一个{value: any, done: boolean}
对象,done
默认值为false
,value
默认值为undefined
:value
能够是任意值done
为true
,表示迭代器已经执行到序列的末尾;done
为false
表示迭代器还能够继续执行next()
方法并返回下一个序列对象实现Iterator接口有不少种方法,不论你的数据结构为类仍是对象,咱们只要保证[Symbol.iterator]属性方法及其返回的数据规范便可,如下为自定义迭代器的示例:
// 使用生成器的方式,推荐使用
const obj = {
[Symbol.iterator] = function* () {
yield 'hello';
yield 'world';
}
}
// 使用纯函数的方式定义返回对象及next方法
const obj = {
[Symbol.iterator]: () => {
const items = ['hello', 'world'];
let nextIndex = 0;
return {
next() {
return nextIndex < items.length
? { value: items[nextIndex++] }
: { done: true };
}
};
}
};
for (item of obj) { console.log(item) }
复制代码
这三种方法都属于Generator的原型方法,经过其对象进行调用,目的是让Generator恢复执行,并使用不一样语句替换当前yield标志所在的表达式:
next(value)
,将表达式替换为value,next函数主要用于向生成器内部传递值,从而改变生成器的状态。throw(error)
,将表达式替换为throw(error),throw方法会将Error对象交由生成器内部处理,若生成器没法处理则会又将错误抛出来,此时会中断生成器的执行。return(value)
,将表达式替换为return value,return方法用于中断生成器的执行。若要一一举例,篇幅可能会很是长,所以在这里举一个包含这三种语句的示例:
function* test() {
const flag = yield 'does anybody here';
if (flag) {
try {
yield 'could you sing for me?';
} catch (e) {
if (e.message === 'sorry!') {
yield 'I can teach you';
} else {
throw e
}
}
yield 'maybe next time';
}
}
const executer = test();
executer.next(); // {value: 'does anybody here?'}
executer.next(true); // { value: 'could you sing for me ?' }
executer.throw(new Error('sorry!')); // {value: 'i will teach you'}
executer.return('thank you'); // {value: "thank you", done: true}
复制代码
函数有几点须要解释: