Generator的语法
generator的英文意思是生成器
简介
关于Generator函数,我们可以理解成是一个状态机,里面封装了多种不同的状态。
function* gener(){
yield 'hello';
yield 'world';
return 'ending';
}
var g = gener(); // g是一个遍历器对象
g.next(); // {value:'hello',done:false}
g.next(); // {value:'world',done:false}
g.next(); // {value:'ending',done:true}
g.next(); // {value:undefined,done:true}
上面代码定义了一个Generator函数,这个函数有两个地方与其他函数不同
- function后边跟一个*
- 函数内部有一个关键字yield。yield表示定义一个状态,所以上边的函数其实有三个状态。
- 调用Generator函数和普通函数一样,都是后面跟圆括号,不过调用Generator函数,函数并不执行,返回的也不是正常的返回结果,而是一个指向内部状态的指针函数,也就是Iterator这个遍历器对象。
来剖析一下上面的代码:
- 上面代码一共执行了四次。调用next()便会执行一次。每次代码执行到yield的时候就会停下来,输出一个对象,对象的value值就是yield后边的值,done这个值表示遍历是否结束,这个时候为false。
- 第二次同理,遇到了第二个yield函数会停下来,输出的遍历器对象value值为'world',done的值依然是false。
- 第三次,当代码遇到了return语句(如果没有return语句,就一直执行到函数结束)这个时候返回的遍历器对象的value的值,就是return后边跟的表达式的值,这个时候因为遍历结束了,所以done的值就变成了true.
- 第四次,因为函数已经return,next()返回的对象的value就是undefined,done值为true
yield的逻辑
理论上,yield提供了一种函数可以暂停的机制。而暂停的表达式就是yield。
- 当代吗执行到yield语句的时候,会暂停不会立即执行,并且把yield后面表达式的值当做返回对象的value属性值返回。
- 当下一次调用next()方法的时候,会从当前暂停的位置继续执行,知道遇到下一个yield或者return语句,返回其后面表达式的值。
- 如果都没有,那么一直执行到代码结束。这个时候返回对象value值是undefined。
- 当遍历结束的时候,done的值会从false变成true.
yield和return的区别?
两者相同点:都是返回跟在其后面的表达式的值。
两者不同点:yield有记忆功能,函数执行完以后会记录下来在从记录的位置继续执行;return并不具有记忆功能,从这返回以后函数不会在执行,仅仅执行一次。而Generator可以返回多个值,返回一系列的值。
function* fun(){
console.log('执行了!')
}
var gen = fun();
setTimeout(function(){
gen.next();
},3000)
这个console要到3s后才执行。
另外,yield语句只能在Generator函数里面。所以,yield也不能放到forEach函数里,也不能放到map函数里。
function funerr(){
yield 123+321
} //报错
再另外,如果yield语句在另外一个表达式里,必须在圆括号里。
function* fun(){
console.log('hello'+yield) // 语法错误
console.log('hello'+(yield)) //正确
}
再再另外,yield作为函数参数或者赋值表达式的右边,可以不用加括号。
next方法的参数
yield本身并没有返回值,但是在next的参数里可以带一个参数,这个参数表示上一个yield语句的返回值。
// next()的参数
function *f() {
for(var i=0;true;i++){
var reset = yield i;
if(reset) {
i = -1
}
}
}
var f = f()
console.log(f.next()) //{value:0,done:false}
console.log(f.next()) //{value:1,done:false}
console.log(f.next()) //{value:2,done:false}
console.log(f.next(true)) //{value:0,done:false}
前边几次输出,这个时候reset的值是undefined,所以,i的值一次增加,当next函数传一个true,代表上一次的yield的值是true,那么这个时候i的值是-1,下一次循环从i等于-1开始.
Generator函数,从暂停状态到恢复运行,上下文是不变的。通过next方法的参数,就有办法在函数运行之后重新往函数里注入值,也就是在函数不同阶段注入不同的值,
function* foo(x) {
var y = yield (x+2)
var z = (yield ((y+3) *2))
return x+y+z
}
var foo1 = foo(5)
console.log(foo1.next()) //{value:7,done:false}
console.log(foo1.next()) //{value:NaN,done:false}
console.log(foo1.next()) // {value:NaN,done:true}
var foo2 = foo(5)
console.log(foo2.next()) // {value:7,done:false} x:5 y:7
console.log(foo2.next(2)) //{value:10,done:false} x:5 y:2
console.log(foo2.next(3)) // {value:10,done:true} x:5 y:2 z:3
执行第二个next方法的时候,这个时候没有传入值,所以这个时候,y的值是undefined。所以。2 * undefined是NaN。
这个地方不是太好理解,可以再举一个例子
function* foo(x) {
var y = 2 * (yield (x+2)) //之前说过的,yield作为表达式一定要在括号里
var z = (yield (y+3) *2)
return x+y+z
}
var foo2 = foo(5)
log(foo2.next())
log(foo2.next(2))
log(foo2.next(3))
用for...of代替next方法
每次总是调用next方法太过麻烦。for...of循环可以自动遍历Generator函数生成的Itearator对象,不需要调用next方法。
function* bar () {
yield 3;
yield 4;
yield 1;
yield 7;
yield 9;
return 0;
}
for (var i of bar()){
console.log(i) // 3,4,1,7,9
}
注意,上面的return语句并不在循环中,因为遍历到done为true的时候就会停止,所以,不会输出0.
理论上,实现了Iterator遍历器接口的扩展运算符(...),结构赋值,Array.from()内部调用的,都可以将Generator的返回值作为参数。比如:
function* foo(){
yield:1;
yield:3;
return 9;
yield:6
}
[...foo()] // [1,3]
Array.from(foo()) // [1,2]
let [x,y] = foo() //x=1 y=2
Generator.prototype.throw()
Generator返回的遍历器对象,都会有一个throw方法,这个方法挂在原型上。作用是在外部抛出在函数内部捕获的错误
function* gg() {
try {
yield ;
} catch (e){
console.log('内部错误',e)
}
}
var gg = gg()
gg.next();
try {
gg.throw('a')
gg.throw('b')
} catch (e){
console.log('外部错误',e)
}
在外部的try语句中,会连续抛出两个错误,第一个错误会被内部捕获,但是到了第二次的时候,因为内部的catch语句已经执行过了,所以就不会再次执行,所以错误会被外部的catch捕获。throw方法接受一个参数,可以再catch里输出,不过一般还是catch输出一个Error对象。
var g = function* () {
try {
yield 8;
} catch (e) {
console.log(e);
}
};
var i = g();
console.log(i.next())
i.throw(new Error('出错了!'));
console.log(i.next(7))
如果在Generator函数内部没有部署try/catch,那么直接会在外部抛出错误,如果内外部都没有try/catch。那么函数直接错误中断执行。
如果函数内部抛出了错误,并不影响接下来yield或者return的执行。
Generator.prototype.return()
这个函数的作用就是return出一个值,并且终结Generator函数的执行。
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() throw() return()
这三个函数共同的作用是让Generator函数恢复执行。
next()就是让yield赋一个值,如果next()有参数,即是给yield传入一个值,如果没有参数,就是undefined。
throw()是把yield表达式替换成一个throw语句。
return()是把yield表达式替换成一个return语句。
理论上,三个函数都是在做同样一件事情。
yield* 表达式
如果在一个Generator函数里调用另外一个Generator函数,默认是没有效果的。
function* foo(){
yield 'xx';
yield 'yy';
}
function* bar(){
yield 'aa';
foo();
yield 'bb';
}
for(var i of bar()){
console.log(i) // aa bb
}
如果想要在bar函数中执行foo函数,需要改写一下bar函数
function bar(){
yield 'aa';
yield* foo()
yield 'bb'
}
输出的会是'aa' 'xx' 'yy' 'bb'
作为对象属性的Generator函数
如果一个对象的属性是Generator函数,可以简写成:
let obj = {
* foo(){
....
}
}
//等价于
let obj = {
foo : function* (){
....
}
}
Generator中的this
Generator函数返回一个遍历器对象,这个遍历器对象是Generator函数的实例,当然也就继承了函数原型上的那些方法。
function* foo(){
yield 'xx'
}
let f = foo()
f00.prototype.hello = function(){
console.log(123)
}
f instanceof foo // true
f.hello() //123
所以,代码可以看出,f是foo的实例,同时可以调用foo原型上重写的方法。
但是:
function* foo(){
this.a = 10
}
let f = foo()
f.a //undefined
new foo() //报错,foo is not a constructor
因为foo()返回的是一个遍历器对象,而不是this.
同时,Generator函数也不能和new一起使用。
那如何既能调用next()又能获取this呢。有一个变通的方法。用call绑定内部的this
function* foo(){
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
let f = foo.call(F.prototype)
f.next() {value:2,done:false}
f.next() {value:3,done:true}
f.next() {valye:undefined,done:true}
f.a 1
f.b 2
f.3 3
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。