转载请注明出处: Generator函数语法解析html
Generator函数是ES6提供的一种异步编程解决方案,语法与传统函数彻底不一样。如下会介绍一下Generator函数。es6
写下这篇文章的目的其实很简单,是想梳理一下本身对于Generator的理解,同时呢,为学习async函数作一下知识储备。编程
对于Generator函数(也能够叫作生成器函数)的理解,能够从四个方面:数组
形式上:Generator函数是一个普通的函数,不过相对于普通函数多出了两个特征。一是在function关键字和函数明之间多了'*'号;二是函数内部使用了yield表达式,用于定义Generator函数中的每一个状态。闭包
语法上:Generator函数封装了多个内部状态(经过yield表达式定义内部状态)。执行Generator函数时会返回一个遍历器对象(Iterator对象)。也就是说,Generator是遍历器对象生成函数,函数内部封装了多个状态。经过返回的Iterator对象,能够依次遍历(调用next方法)Generator函数的每一个内部状态。app
调用上:普通函数在调用以后会当即执行,而Generator函数调用以后不会当即执行,而是会返回遍历器对象(Iterator对象)。经过Iterator对象的next方法来遍历内部yield表达式定义的每个状态。异步
写法上:*号放在哪里好像均可以也。看我的习惯吧,我喜欢第一种写法async
function *gen () {} √ function* gen () {} function * gen () {} function*gen () {}
yield,英文意思即产生、退让的意思,所以yield表达式也有两种做用:定义内部状态和暂停执行。异步编程
举一个栗子吧: )函数
function *gen () { yield 1 yield 2 return 3 } const g = gen() // Iterator对象 g.next() // {value: 1, done: false} g.next() // {value: 2, done: false} g.next() // {value: 3, done: true}
从上面代码中能够看出,gen函数使用yield表达式定义了两个内部状态。同时呢,也能够看出来,return语句只能有一个,而yield表达式却能够有多个。
执行gen函数以后,会返回一个遍历器对象,而不是当即执行gen函数。若是须要获取yield表达式定义的每一个状态,须要调用next方法。
每调用一次next方法都会返回一个包含value和done属性的对象,此时会停留在某个yield表达式结尾处。value属性值便是yield表达式的值;done属性是布尔值,表示是否遍历完毕。
另外呢,yield表达式没有返回值,或者说返回值是undefined。待会会说明一下如何给yield表达式传递返回值。
须要注意的是,yield表达式的值,只有调用next方法时才能获取到。所以等于为JavaScript提供了手动的'惰性求值'(Lazy Evaluation)的功能。
通常状况下,Generator函数会结合yield表达式使用,经过yield表达式定义多个内部状态。可是,若是不使用yield表达式的Generator函数就成为了一个单纯的暂缓执行函数,我的感受没什么意义...
function *gen () { console.log('凯斯') } window.setTimeout(() => { gen().next() }, 2000) // 不使用yield表达式来暂停函数的执行,还不如使用普通函数呢.. // 因此Generator函数配合yield表达式使用效果更佳
另外,yield表达式若是用在另外一个表达式中,须要为其加上圆括号。做为函数参数和语句是能够不使用圆括号。
function *gen () { console.log('hello' + yield) × console.log('hello' + (yield)) √ console.log('hello' + yield '凯斯') × console.log('hello' + (yield '凯斯')) √ foo(yield 1) √ const param = yield 2 √ }
yield表达式具备暂停执行的功能,而恢复执行的是next方法。每一次调用next方法,就会从函数头部或者上一次停下来的地方开始执行,直到遇到下一个yield表达式(return 语句)为止。同时,调用next方法时,会返回包含value和done属性的对象,value属性值能够为yield表达式、return语句后面的值或者undefined值,done属性表示遍历是否结束。
遍历器对象的next方法(从Generator函数继承而来)的运行逻辑以下
从上面的运行逻辑能够看出,返回的对象的value属性值有三种结果:
也就是说,若是有yield表达式,则value属性值就是yield表达式后面的指;若是没有yield表达式,value属性值就等于return语句后面的值;若是yield表达式和return语句都不存在的话,则value属性值就等于undefined。举个例子: )
function *gen () { yield 1 yield 2 return 3 } const g = gen() g.next() // {value: 1, done: false} g.next() // {value: 2, done: false} g.next() // {value: 3, done: true} g.next() // {value: undefined, done: true}
根据next运行逻辑再针对这个例子,就很容易理解了。调用gen函数,返回遍历器对象。
第一次调用next方法时,在遇到第一个yield表达式时中止执行,value属性值为1,即yield表达式后面的值,done为false表示遍历没有结束;
第二次调用next方法时,从暂停的yield表达式后开始执行,直到遇到下一个yield表达式后暂停执行,value属性值为2,done为false;
第三次调用next方法时,从上一次暂停的yield表达式后开始执行,因为后面没有yield表达式了,因此遇到return语句时函数执行结束,value属性值为return语句后面的值,done属性值为true表示已经遍历完毕了。
第四次调用next方法时,value属性值就是undefined了,此时done属性为true表示遍历完毕。之后再调用next方法都会是这两个值。
yield表达式自己没有返回值,或者说老是返回undefined。
function *gen () { var x = yield 'hello world' var y = x / 2 return [x, y] } const g = gen() g.next() // {value: 'hello world', done: false} g.next() // {value: [undefined, NaN], done: true}
从上面代码能够看出,第一次调用next方法时,value属性值是'hello world',第二次调用时,因为变量y的值依赖于变量x,而yield表达式没有返回值,因此返回了undefined给变量x,此时undefined / 2为NaN。
要解决上面的问题,能够给next方法传递参数。next方法能够带一个参数,该参数就会被看成上一个yield表达式的返回值。
function *gen () { var x = yield 'hello world' var y = x / 2 return [x, y] } const g = gen() g.next() // {value: 'hello world', done: false} g.next(10) // {value: [10, 5], done: true}
当给第二个next方法传递参数10时,yield表达式的返回值为10,即var x = 10
,因此此时变量y为5。
注意,因为next方法的参数表示上一个yield表达式的返回值,因此在第一次使用next方法时,传递参数是无效的。V8引擎直接忽略第一次使用next方法的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上说,第一个next方法用来启动遍历器对象,因此不用带上参数。因此呢,每次使用next方法会比yield表达式要多一次。
若是想要第一次调用next方法时就能够传递参数,可使用闭包的方式。
// 实际上就是在闭包内部执行了一次next方法 function wrapper (gen) { return function (...args) { const genObj = gen(...args) genObj.next() return genObj } } const generator = wrapper(function *generator () { console.log(`hello ${yield}`) return 'done' }) const a = generator().next('keith') console.log(a) // hello keith, done
实际上,yield表达式和next方法构成了双向信息传递。yield表达式能够向外传递value值,而next方法参数能够向内传递值。
function *foo () { yield 1 } function *gen () { foo() yield 2 } const g = gen() g.next() // {value: 2, done: false} g.next() // {value: undefined, done: true}
从上面代码中能够看出,并无在yield 1
处中止执行。此时就须要使用yield* 表达式。从语法角度上说,若是yield表达式后面跟着遍历器对象,须要在yield表达式后面加上星号,代表它返回的是一个遍历器对象。实际上,yield*表达式是for...of循环的简写,彻底可使用for...of循环来代替yield*表达式
function *foo () { yield 1 } function *gen () { yield* foo() yield 2 } const g = gen() g.next() // {value: 1, done: false} g.next() // {value: 2, done: false} g.next() // {value: undefined, done: true} // 至关于 function *gen () { yield 1 yield 2 } // 至关于 function *gen () { for (let item of foo()) { yield item } yield 2 }
若是直接使用了yield foo()
,返回的对象的value属性值为一个遍历器对象。而不是Generator函数的内部状态。
function *foo () { yield 1 } function *gen () { yield foo() yield 2 } const g = gen() g.next() // {value: Generator, done: false} g.next() // {value: 2, done: false} g.next() // {value: undefined, done: true}
另外,任何数据类型(Array, String)只要有Iterator接口,就可以被yield*
遍历
const arr = ['a', 'b'] const str = 'keith' function *gen () { yield arr yield* arr yield str yield* str } const g = gen() g.next() // {value: ['a', 'b'], done: false} g.next() // {value: 'a', done: false} g.next() // {value: 'b', done: false} g.next() // {value: 'keith', done: false} g.next() // {value: 'k', done: false} ...
若是在Generator函数中存在return语句,则须要使用let value = yield* iterator
方式获取返回值。
function *foo () { yield 1 return 2 } function *gen () { var x = yield* foo() return x } const g = gen() g.next() // {value: 1, done: false} g.next() // {value: 2, done: true}
使用yield*表达式能够很方便的取出嵌套数组的成员。
// 普通方法 const arr = [1, [[2, 3], 4]] const str = arr.toString().replace(/,/g, '') for (let item of str) { console.log(+item) // 1, 2, 3, 4 } // 使用yield*表达式 function *gen (arr) { if (Array.isArray(arr)) { for (let i = 0; i < arr.length; i++) { yield * gen(arr[i]) } } else { yield arr } } const g = gen([1, [[2, 3], 4]]) for (let item of g) { console.log(item) // 1, 2, 3, 4 }
任何一个对象的Symbol.iterator
属性,指向默认的遍历器对象生成函数。而Generator函数也是遍历器对象生成函数,因此能够将Generator函数赋值给Symbol.iterator
属性,这样就使对象具备了Iterator接口。默认状况下,对象是没有Iterator接口的。
具备Iterator接口的对象,就能够被扩展运算符(...),解构赋值,Array.from和for...of循环遍历了。
const person = { name: 'keith', height: 180 } function *gen () { const arr = Object.keys(this) for (let item of arr) { yield [item, this[item]] } } person[Symbol.iterator] = gen for (let [key, value] of person) { console.log(key, value) // name keith , height 180 }
Generator函数函数执行以后,会返回遍历器对象。该对象自己也就有Symbol.iterator
属性,执行后返回自身
function *gen () {} const g = gen() g[Symbol.iterator]() === g // true
for...of循环能够自动遍历Generator函数生成的Iterator对象,不用调用next方法。
function *gen () { yield 1 yield 2 yield 3 return 4 } for (let item of gen()) { console.log(item) // 1 2 3 }
上面代码使用for...of循环,依次显示 3 个yield表达式的值。这里须要注意,一旦next方法的返回对象的done属性为true,for...of循环就会停止,且不包含该返回对象,因此上面代码的return语句返回的6,不包括在for...of循环之中。
若是一个对象有Generator函数,那么可使用简写方式
let obj = { * gen () {} } // 也能够完整的写法 let obj = { gen: function *gen () {} }
固然了,若是是在构造函数中,简写形式也是同样的。
class F { * gen () {} }
Generator函数中的this对象跟构造函数中的this对象有殊途同归之处。先来看看构造函数中的new关键字的工做原理。
function F () { this.a = 1 } const f = new F()
调用Generator函数会返回遍历器对象,而不是实例对象,所以没法获取到this指向的实例对象上的私有属性和方法。可是这个遍历器对象能够继承Generator函数的prototype原型对象上的属性和方法(公有属性和方法)。
function *Gen () { yield this.a = 1 } Gen.prototype.say = function () { console.log('keith') } const g = new Gen() g.a // undefined g.say() // 'keith'
若是但愿修复this指向性问题,可使用call方法将函数执行时所在的做用域绑定到Generator.prototype原型对象上。这样作,会使私有属性和方法变成公有的了,由于都在原型对象上了。
function *Gen () { this.a = 1 yield this.b = 2 yield this.c = 3 } const g = Gen.call(Gen.prototype) g.next() // {value: 2, done: false} g.next() // {value: 3, done: false} g.next() // {value: undefined, done: true} g.a // 1,继承自Gen.prototype g.b // 2,同上 g.c // 3,同上
Generator函数的应用主要在异步编程上,会在下一篇文章中分享。请期待噢: )