文章目录
Generator 函数的语法
1.0 简介
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,
function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态(yield
在英语里的意思就是“产出”)。
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();
Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
下一步,必须调用遍历器对象的
next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
2.0 yield表达式
由于 Generator 函数返回的遍历器对象,只有调用
next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志
遍历器对象的
next
方法的运行逻辑如下。
- 遇到
yield
表达式, 就暂停执行后面的操作, 并将紧跟在yield
后面的那个表达式的值, 作为返回的对象的value
的属性值.- 下一次调用
next
方法时, 再继续向下执行, 直到遇到下一个yield
表达式再次暂停, 并获取到yield
之后的值.- 如果没有再遇到 新的
yield
表达式, 就一直运行到函数结束, 直到return
语句为止, 并将return
语句后面的表达式的值, 作为返回对象的value
的属性值.- 如果该函数没有
return
语句, 则返回对象的value
的属性值为undefined
需要注意的是,
yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。function* findGenerator() { const first = yield 2 // console.log('first ===>>>', first); // first为 undefined说明 yield 后面的值 直接返回给了外部调用next()方法的指针对象 yield 3 } ge = findGenerator() console.log(ge.next()); // {value: 2, done: false} 然后暂停, 函数内部的 console语句不再执行. console.log(ge.next()); // first ===>>> undefined {value: 3, done: false} // 第二次调用next方法, 打印了函数体内的打印语句, 和 yield 3 之后的值, 说明yield 2处被暂停, 再调用next() 方法之后, 它之后的代码才执行
yield
表达式与return
语句既有相似之处,也有区别
- 相似之处在于,都能返回紧跟在语句后面的那个表达式的值
- 区别在于每次遇到
yield
,函数暂停执行,下一次再从该位置继续向后执行,而return
语句不具备位置记忆的功能 一个函数里面,只能执行一次(或者说一个)return
语句,但是可以执行多次(或者说多个)yield
表达式。- 正常函数只能返回一个值,因为只能执行一次
return
;Generator 函数可以返回一系列的值,因为可以有任意多个yield
。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。
Generator 函数可以不用
yield
表达式, 这时就变成了一个单纯的暂缓执行函数.function* f() { console.log('执行了!') } var generator = f(); setTimeout(function () { generator.next() // 只有调用 next() 方法 generator 函数 f 内部才会执行 }, 500);
上面代码中,函数
f
如果是普通函数,在为变量generator
赋值时就会执行,但是,函数
f
是一个 Generator 函数,就变成只有调用next
方法时,函数f
才会执行。
另外需要注意,
yield
表达式只能用在 Generator 函数里面,用在其他地方都会报错。
3.0 next 方法的参数
yield
表达式, 本身没有返回值, 或者说总是返回undefined
,next
方法可以带一个参数, 该参数就会被当做上一个yield
表达式的放回值.function * nextParamGenerator() { yield 2 const param1 =yield 3 console.log(param1); // next函数传入参数, 会作为上一个 yield 表达式的返回值 yield 4 } const gen = nextParamGenerator() console.log(gen.next()); // {value: 2, done: false} console.log(gen.next()); // {value: 3, done: false} console.log(gen.next('next函数传入参数, 会作为上一个 yield 表达式的返回值'));
4.0 for … of 循环
for...of
循环可以自动遍历 Generator 函数运行时生成的Iterator
对象,且此时不再需要调用next
方法。function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let value of foo()) { console.log(value); // // 1 2 3 4 5 }
上面代码使用
for...of
循环,依次显示 5 个yield
表达式的值。这里需要注意,一旦next
方法的返回对象的done
属性为true
,for...of
循环就会中止,且不包含该返回对象,所以上面代码的return
语句返回的6
,不包括在for...of
循环之中。
5.0 Generator.prototype.throw()
Generator 函数返回的遍历器对象,都有一个
throw
方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。var g = function* () { try { yield; } catch (e) { console.log('内部捕获', e); } }; var i = g(); i.next(); try { i.throw('a'); i.throw('b'); } catch (e) { console.log('外部捕获', e); } // 内部捕获 a // 外部捕获 b
上面代码中,遍历器对象
i
连续抛出两个错误。第一个错误被 Generator 函数体内的catch
语句捕获。i
第二次抛出错误,由于 Generator 函数内部的catch
语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch
语句捕获。
throw
方法可以接受一个参数,该参数会被catch
语句接收,建议抛出Error
对象的实例var g = function* () { try { yield; } catch (e) { console.log(e); } }; var i = g(); i.next(); i.throw(new Error('出错了!')); // Error: 出错了!(…)
6.0 Generator.prototype.return()
Generator 函数返回的遍历器对象,还有一个
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 }
上面代码中,遍历器对象
g
调用return
方法后,返回值的value
属性就是return
方法的参数foo
。并且,Generator 函数的遍历就终止了,返回值的done
属性为true
,以后再调用next
方法,done
属性总是返回true
。如果
return
方法调用时,不提供参数,则返回值的value
属性为undefined
。
7.0 next(), throw(), return()的共同点
next()
、throw()
、return()
这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield
表达式。
next()
是将yield
表达式替换成一个值。const g = function* (x, y) { let result = yield x + y; return result; }; const gen = g(1, 2); gen.next(); // Object {value: 3, done: false} gen.next(1); // Object {value: 1, done: true} // 相当于将 let result = yield x + y // 替换成 let result = 1;
上面代码中,第二个
next(1)
方法就相当于将yield
表达式替换成一个值1
。如果next
方法没有参数,就相当于替换成undefined
。
throw()
是将yield
表达式替换成一个throw
语句。gen.throw(new Error('出错了')); // Uncaught Error: 出错了 // 相当于将 let result = yield x + y // 替换成 let result = throw(new Error('出错了'));
return()
是将yield
表达式替换成一个return
语句gen.return(2); // Object {value: 2, done: true} // 相当于将 let result = yield x + y // 替换成 let result = return 2;
8.0 yield* 表达式
如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; // 手动遍历 foo() for (let i of foo()) { console.log(i); } yield 'y'; } for (let v of bar()){ console.log(v); } // x // a // b // y
上面代码中,
foo
和bar
都是 Generator 函数,在bar
里面调用foo
,就需要手动遍历foo
。如果有多个 Generator 函数嵌套,写起来就非常麻烦。
ES6 提供了
yield*
表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。function* bar() { yield 'x'; yield* foo(); yield 'y'; } // 等同于 function* bar() { yield 'x'; yield 'a'; yield 'b'; yield 'y'; } // 等同于 function* bar() { yield 'x'; for (let v of foo()) { yield v; } yield 'y'; } for (let v of bar()){ console.log(v); } // "x" // "a" // "b" // "y"
9.0 作为对象属性的 Generator 函数
如果一个对象的属性是 Generator 函数, 可以简写成下面的两种格式
let obj = { * myGeneratorMethod() { ··· } };
上面代码中,
myGeneratorMethod
属性前面有一个星号,表示这个属性是一个 Generator 函数。它的完整形式如下,与上面的写法是等价的。
let obj = { myGeneratorMethod: function* () { // ··· } };
10.0 Generator 函数的this
Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的
prototype
对象上的方法。function* g() {} g.prototype.hello = function () { return 'hi!'; }; let obj = g(); obj instanceof g // true obj.hello() // 'hi!'
上面代码表明,Generator 函数
g
返回的遍历器obj
,是g
的实例,而且继承了g.prototype
。但是,如果把g
当作普通的构造函数,并不会生效,因为g
返回的总是遍历器对象,而不是this
对象。function* g() { this.a = 11; } let obj = g(); obj.next(); obj.a // undefined
Generator 函数也不能跟
new
命令一起用,会报错。
= g();
obj instanceof g // true
obj.hello() // ‘hi!’上面代码表明,Generator 函数`g`返回的遍历器`obj`,是`g`的实例,而且继承了`g.prototype`。但是,如果把`g`当作普通的构造函数,并不会生效,因为`g`返回的总是遍历器对象,而不是`this`对象。 ```js function* g() { this.a = 11; } let obj = g(); obj.next(); obj.a // undefined
Generator 函数也不能跟
new
命令一起用,会报错。
总结:
交流学习添加微信(备注技术交流学习):
Gene199302
该博客为学习阮一峰 ES6入门课所做的笔记记录, 仅用来留作笔记记录和学习理解