ES6新增特性,用于实现iterator(迭代器),是可以暂停执行的函数
- Generator函数是一个状态机,内部封装了不同状态时的数据
- 同时也是迭代器对象生成函数,可以一次遍历 Generator 函数内部的每一个状态
语法
// 生成器函数声明function* generator () {yield '第一段'yield* '第二段'return '结束了'}// 生成器函数表达式let generatorFn = function* () {}// 作为对象字面量方法的生成器函数let foo = {* generatorFn() {}}// 作为类实例方法的生成器函数class Foo {* generatorFn() {}}// 作为类静态方法的生成器函数class Bar {static * generatorFn() {}}
- function关键字后面,或函数名前加一个星号
*,即可定义Generator函数- 只要是可以定义函数的地方,都可以定义生成器
- 箭头函数不能用来定义生成器函数
- 星号两侧的空格不影响定义
Generator函数调用后并不会执行,而是会返回一个生成器对象,该对象实现了 Iterator。
函数内部有一个
yield关键字,用于定义yield表达式- yield表达式用于定义不同的状态,是暂停标识
const interator = generator() const { value, done } = interator.next(value)
- yield表达式用于定义不同的状态,是暂停标识
Generator函数调用后不会执行代码,而是会返回一个迭代器对象(Iterator Object)
- 通过调用迭代器对象的方法, 使得指针移向下一个状态,执行代码
- 迭代器的next方法返回一个对象,包含value和done两个属性(iterator原理)
- value:yield表达式的结果,return的值
- done:是否遍历结束,暂停false,函数执行完成或return是true
next方法运行逻辑
- 遇到 yield表达式,就暂停函数的执行,并将
yield后面的表达式的结果,作为返回对象的value值 - 下一次调用next时,从上一次暂停的 yield表达式 继续向后执行,直到遇到下一个yield
- 如果函数全部执行完成或遇到了return语句,就将return的值,作为value的值
注意点
- 遇到yield表达式时,只会执行yield后面的表达式,该行语句中的其他代码都不会执行
只有当下一次调用next,内部指针指向该行语句时,剩余的代码才会被执行
function* gen() { const a = yield 123 - 456 // { value: -333, done: false } const b = (yield 123 + 456) + 1 // { value: 579, done: false } }上面代码中,执行到第一行时,会暂停执行,并获取 yield 后面的表达式
123 - 456的结果cont a =则不会执行,直到下一次next时,才会被执行
- 如果 yield表达式 使用括号了特别声明,那么暂停时,括号之外的也不会被执行
- 如遇到第二行的yield时,只会执行
123 + 456,其他的等下一下next
- 如遇到第二行的yield时,只会执行
next方法的参数
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以传一个参数,该参数会被当做上一个yield表达式的返回值
function* dataConsumer() {
console.log('Started')
console.log('1.' + ( yield ))
console.log('2.' + ( yield ))
return 'result'
}
let genObj = dataConsumer()
genObj.next() // Started
genObj.next() // 1.undefined
genObj.next('b') // 2.b
for…of循环
for…of可以自动遍历Generator生成的遍历对象,不需要自己调用next方法
function* foo() {
yield 1
yield 2
return 3
}
for (let v of foo()) {
console.log(v) // 1 2
}
- 当next方法返回对象的done属性为true时,就会中止循环,且这次的返回对象不会参与循环
- 上面代码中的6就没有打印
- 每次循环会自动将返回对象中的value取出
遍历接口
原生JS对象没有 Iterator 接口,无法使用for..of,可以通过Generator 为它添加这个接口
function* objectEntries() {
let propKeys = Object.keys(this)
for (let propKey of propKeys) {
yield [propKey, this[propKey]]
}
}
let jane = { first: 'Jane', last: 'Doe' }
jane[Symbol.iterator] = objectEntries
for (let [key, value] of jane) {
console.log(`${key}: ${value}`)
}
其他语法
扩展运算符(…)、解构赋值和Array.from方法内部都是使用的遍历接口,因此Generator返回的遍历对象,可以作为参数
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 扩展运算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解构赋值
let [x, y] = numbers();
x // 1
y // 2
迭代器对象(interator)API
ES6规定返回的迭代器是 Generator 函数的实例,也继承了Generator的原型
Generator.prototype.throw()
迭代器对象有一个 throw方法,可以在函数体外抛出错误,并在函数体内进行捕获
function* g () {
try {
yield 'Started'
} catch(e) {
console.log('内部捕获', e)
}
}
const i = g()
i.next()
try {
i.throw(new Error(777));
i.throw('a');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 Error: 出错了!(…)
// 外部捕获 a
- 调用该方法后,会在上一个yield处抛出错误,因此可以在函数体内进行捕获
- 第一个被函数体内的catch捕获了
- 第二个由于函数体内部的catch已经执行过了,无法再进行捕获,因此这个错误就被抛到了函数外部
- throw方法可以接收一个参数,该参数就是错误信息,建议是一个 Error 实例
- throw抛出的错误要被内部捕获,必须要调用一次next方法
- 因为需要执行函数,让内部指向内try…catch包裹的yield表达式
- 如果捕获了异常,代码会从catch继续向下执行
- 如果Generator函数中的错误没有进行捕获,那么这个函数将结束遍历
Generator.prototype.return()
迭代器对象有一个 return方法,可以终结遍历
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
- 调用后返回一个对象,同next
return()可以接收一个参数,会成为返回对象的value的值,如果不传就是 undefined- 调用return后,返回对象的done为true,之后再调用next,done总是为true
next、throw、return共同点
next()、throw()、return()这三个方法本质上是做相同的事
- 使用不同语句替换上一个yield表达式
- 让Generator函数恢复执行 ```javascript const g = function* (x, y) { let result = yield x + y; return result; }
const gen = g(1, 2) gen.next(); // Object {value: 3, done: false}
`next()` 是将yield表达式替换成一个值
```javascript
gen.next(1);
// 将 yield x + y 替换为1
// let result = 1
throw() 是将yield表达式替换成一个throw语句
gen.throw(new Error('出错了'))
// 将 yield x + y 替换为 throw new Error('出错了')
// let result = throw new Error('出错了')
return() 是将yield表达式替换为一个return语句
gen.return(2)
// 将 yield x + y 替换为 return(2)
// // let result = return(2)
