这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战
问题
面试官问:你能讲讲什么是async函数吗?
面试者:巴拉巴拉一大堆。。。,它是Generator函数的语法糖。
面试官: 那你能讲讲Generator函数吗,为什么说是它的语法糖?
面试者: 。。。
以上这个场景纯属虚构,可能会有雷同。
今天咱们来学学Generator函数,如果遇到上面这个场景我们就可以对面试官侃侃而谈。
Generator函数
定义
Generator函数和普通函数不同,普通函数执行时函数的内容会全部执行完,一般不可以在中途中断它(除非报错,这种一般不考虑),而调用Generator函数并不会执行函数,而是返回一个遍历器对象,只有调用它的next方法,函数才会执行,如果这个函数里面有多个状态(通过yield关键字定义),需要执行多个next方法。
等同于一个同步函数,后一个状态只有等前一个状态执行后才能执行,以此类推。
语法
function* Fn() {
console.log(1)
yield '答案cp3'
console.log(2)
yield '18'
console.log(3)
return 'good boy'
}
var fn = Fn()
fn.next() // 打印1, 返回{value: '答案cp3', done: false}
fn.next() // 打印2, 返回{value: '18', done: false}
fn.next() // 打印3, 返回{value: 'good boy', done: true}
fn.next() // {value: undefined, done: true}
fn.next() // {value: undefined, done: true}
它的定义方式是通过function加上*号,然后内部是使用yield定义状态,前一个状态执行后,后一个状态才可以执行,顺序执行。
调用Generator函数,并不会执行函数,而是返回遍历器对象,原型上常用的有三个方法,next方法,return方法,throw方法。
next方法一般是和yield关键字配合,执行一次next方法,会去找第一个yield关键字,执行它之前的代码,再执行一次next方法,会去找第二个yield关键字,执行两个yield之间的代码,依次类推,直到结尾。
next方法 和 yield
执行next方法会返回一个对象,包含value属性和done属性。
yield 右边一般跟着表达式或者值,它们的值会当作执行next方法后返回的value属性,如果没有值,则是undefined。
done属性是表明当前是否函数已经执行结束,如果最后有return, return的值就是最后的value值。
function* Fn() {
yield '答案cp3'
yield
return 'good boy'
}
var fn = Fn()
fn.next() // {value: '答案cp3', done: false}
fn.next() // {value: undefined, done: false}
fn.next() // {value: 'good boy', done: true}
yield执行时默认是返回undefined的,如果通过next传参,参数是yield返回的值。
function* Fn() {
var num = yield 1+2
yield num * 2
return
}
var fn = Fn()
fn.next() // value: 3
fn.next() // value: NaN undefined * 2 等于NaN
var fn = Fn()
fn.next() // value: 3
fn.next(10) // value: 20
return方法
因为正常是需要next方法一步一步执行到结束,如果想中间中断的话,可以使用return方法。 支持传参,参数是返回的对象的value值。
例子如下:
function* Fn() {
yield '答案cp3'
yield
return 'good boy'
}
var fn = Fn()
fn.next() // {value: '答案cp3', done: false}
fn.return('end') // {value: 'end', done: true} 中止了,done是true
fn.next() // {value: undefined, done: true}
throw方法
可以通过throw方法在Generator函数中抛出错误,一旦抛出错误,如果没有try-catch捕获,Generator函数就中止了。 如果捕获了,会额外执行一次next方法。
function* Fn() {
yield 1
yield 2
return 3
}
var fn = Fn()
fn.next() // {value: 1, done: false}
fn.throw('i am error message') // Uncaught i am error message fn: {value: undefined, done: true}
function* Fn() {
try{
yield 1
} catch (e) {
console.log(e) // i am error message
}
yield 2
}
var fn = Fn()
fn.next() // {value: 1, done: false}
fn.throw('i am error message') // {value: 2, done: true}
如果需要捕获throw方法,则需要next方法先执行一遍。
function* Fn() {
try{
yield 1
} catch (e) {
console.log(e) // i am error message
}
yield 2
}
var fn = Fn()
fn.throw('i am error message')
for-of,扩展运算符,Array.from,解构等
for-of可以直接遍历Generator函数,因为Generator函数执行时返回的是Iterator对象,非常方便。
function* Fn() {
yield 1
yield 2
yield 3
return 4
}
var fn = Fn()
for(let v of fn) {
console.log(v) // 1 2 3
}
// 扩展运算符也行
[...fn]
// 解构也行
[one, two] = fn
注意,结果是不包含return的返回值的。
补充
Generator函数无法使用new 实例化
function* Fn() {
yield 1
yield 2
yield 3
return 4
}
new Fn() // Fn is not a constructor
例外,还有一个yield* fn(), fn是generator函数, 代表的generator函数可以嵌套generator函数。
如下:
function* Fn1() {
yield 1
yield 2
}
function* Fn2() {
yield* Fn1()
yield 3
yield 4
}
[...Fn2()] // [1,2,3,4]