本系列属于阮一峰老师所著的学习笔记
概念
Generator函数是ES6提供的一种异步编程解决方案。形式上,Generator函数是一个普通函数,但是有两个特征:function
关键字与函数名之间有一个星号;函数体内部使用yield
表达式,定义不同的内部状态(yield
表意产出)。
function* helloWorldGenerator(){ yield 'hello' yield 'world' return 'ending'}var hw = helloWorldGenerator()// Generator函数和普通函数一样调用,但是调用后函数并不执行,返回一个指向内部状态的指针对象,即遍历器对象(Iterator Object)hw.next() // {value: 'hello',done:false}hw.next() // {value: 'world',done:false}hw.next() // {value: 'ending',done:true}hw.next() // {value: undefined,done:true}// 每次调用next方法,执行Generator函数,依次返回yeild表达式的值,执行到return语句(若没有return语句就执行到结束)
yield表达式
Generator函数返回遍历器对象,只有调用next
方法才会遍历下一个状态,yield
表达式就是暂停函数执行的暂停标志。
Generator函数可以不用yield
表达式,这时就变成一个单纯的暂缓执行函数
function* f(){ console.log('done!')}var generator = f() // 函数不会立即执行,只有调用next方法才会执行setTimeout(function(){ generator.next()},2000)
yield
表达式如果在另一个表达式中,必须放在圆括号内
function* demo(){ console.log('Hello' + yield) // SyntaxError console.log('Hello' + (yield)) // OK}// yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号function* demo(){ foo(yield 'a',yield 'b') // OK let input = yield // OK}
与Iterator接口的关系
任何一个对象的Symbol.iterator
方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。由于Generator函数就是遍历器生成函数,因此可以把Generator函数赋值给Symbol.iterator
属性,从而使得该对象具有iterator接口。
var myIterator = {}myIterator[Symbol.iterator] = function* (){ yield 1 yield 2 yield 3}[...myIterator] // [1,2,3]
next方法的参数
yield
表达式本身没有返回值,或者说每次都返回undefined。next
方法可以带一个参数,该参数会被当做上一个yield
表达式的返回值
function* f() { for(var i = 0; true; i++) { var reset = yield i; if(reset) { i = -1; } }}var g = f();g.next() // { value: 0, done: false }g.next() // { value: 1, done: false }g.next(true) // { value: 0, done: false }// 每次运行到yield表达式,变量reset总被赋值undefined,当next带上参数之后,遍历reset就被赋值为true
for...of循环
for...of
循环可以自动遍历Generator函数生成的Iterator
对象,且不用再调用next
方法
function* foo(){ yield 1 yield 2 yield 3 yield 4 return 5}for(let v of foo()){ console.log(v)}// 1 2 3 4// for...of循环遇到next方法返回对象的done为true就会中止,所以return返回的5不在循环之中// 除了for...of循环外,拓展运算符...、解构赋值和Array.from都可以将Generator函数返回的Iterator对象作为参数function* numbers () { yield 1 yield 2 return 3 yield 4}// 拓展运算符[...numbers()] // [1,2]// Array.fromArray.from(numbers()) // [1,2]// 解构赋值let [x,y] = numbers()x // 1y // 2// for...of循环for(let n of numbers()){ console.log(n) // 1 2}
Generator.prototype.throw()
Generator函数返回的遍历器对象有一个throw
方法,可以在函数体外抛出错误,然后在函数体内捕获
var g = function* (){ try{ yield }catch(e){ console.log('内部捕获',e) }}var i = g()i.next()// throw方法可以接收一个参数,改参数会被catch语句接收,建议抛出Error对象实例try { i.throw('a') i.throw('b')}catch(e){ console.log('外部捕获',e)}// 内部捕获 a// 外部捕获 b// throw方法被捕获后会附带执行下一条yield表达式,也就是说会附带执行一次next方法
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}// 如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码执行完之后再执行function* numbers(){ yield 1 try{ yield 2 yield 3 }finally{ yield 4 yield 5 } yield 6}var g = numbers()g.next() // {value:1,done:false}g.next() // {value:2,done:false}g.return(7) // {value:4,done:false}g.next() // {value:5,done:false}g.next() // {value:7,done:true}
yield* 表达式
在Generator函数内调用另一个Generator函数,默认情况下是没有效果的,这时候就要用到yield*
表达式
function* inner(){ yield 'hello!'}function* outer1(){ yield 'open' yield inner() yield 'close'}var gen = outer1()gen.next().value // 'open'gen.next().value // 返回一个遍历器对象gen.next().value // 'close'function* outer2(){ yield 'open' yield* inner() yield 'close'}var gen = outer2()gen.next().value // 'open'gen.next().value // 'hello'gen.next().value // ’close'function* concat(iter1,iter2){ yield* iter1 yield* iter2}// 等同于function* concat(iter1,iter2){ for(var value of iter1){ yield value } for(var value of iter2){ yield value }}function* gen(){ yield* ['a','b','c']}gen().next() // {value:'a',done:false}// 若不加星号返回的是整个数组,加了就表示返回的是数组的遍历器对象。实际上任何具有Iterator接口的数据结构都可以被yield遍历function *foo() { yield 2; yield 3; return "foo";}function *bar() { yield 1; var v = yield *foo(); console.log( "v: " + v ); yield 4;}var it = bar();it.next()// {value: 1, done: false}it.next()// {value: 2, done: false}it.next()// {value: 3, done: false}it.next();// "v: foo"// {value: 4, done: false}it.next()// {value: undefined, done: true}//被代理的Generator函数foo有return语句,那么就会向代理它的Generator函数bar返回数据,并且继续执行next方法
作为对象属性的Generator函数
let obj = { * myGeneratorMethod(){ ... }}
Generator函数的this
// 生成空对象,使用call方法绑定Generator函数内部的thisfunction* F(){ this.a = 1 yield this.b = 2 yield this.c = 3}var obj = {}var f = F.call(obj)// 调用三次next方法完成F内部所有代码的运行,将所有内部属性绑定在obj对象上f.next() // {value:2,done:false}f.next() // {value:3,done:false}f.next() // {value:undefined,done:true}// obj对象编程了F的实例obj.a // 1obj.b // 2obj.c // 3// 将F改成构造函数,可以执行new命令function* gen(){ this.a = 1 yield this.b = 2 yield this.c = 3}function F(){ return gen.call(gen.prototype)}var f = new F()f.next() // {value:2,done:false}f.next() // {value:3,done:false}f.next() // {value:undefined,done:true}f.a // 1f.b // 2f.c // 3