首页 > 代码库 > JS魔法堂:ES6新特性——GeneratorFunction介绍

JS魔法堂:ES6新特性——GeneratorFunction介绍

一、前言                                

  第一次看koajs的示例时,发现该语句 function *(next){...............} ,这是啥啊?于是搜索一下,原来这是就是ES6的新特性Generator Function(生成器函数)。

  那什么是生成器函数呢?其实就相当于C#2.0中通过yield关键字实现的迭代器生成器(细节有所不同),那么理解的关键就在yield关键字了。下面将尝试从表象出发,逐步对生成器函数及利用它进行异步编程进行浅层的分析理解。

 

二、语法及基本使用                       

  示例:

// 定义生成器函数function *enumerable(msg){  console.log(msg)  var msg1 = yield msg +   after   console.log(msg1)  var msg2 = yield msg1 +  after  console.log(msg2 +  over)}// 初始化迭代器var enumerator = enumerable(hello)var ret = enumerator.next() // 控制台显示 hello,ret的值{value:‘hello after‘,done:false}ret =  enumerator.next(world) // 控制台显示 world,ret的值{value:‘world after‘,done:false}ret = enumerator.next(game) // 控制台显示game over,ret的值{done:true}// for...of语句enumerator = enumerable(hello)for(ret of enumerator)  console.log(JSON.stringify(ret));// 控制台一次显示// {value:‘hello after‘,done:false}// {value:‘world after‘,done:false}// {done:true}

  1. 生成器语函数定义

function* test(){}function * test(){}function *test(){}test = function* (){} test = function *(){}

  普通函数添加*号后则成为了成为了生成器函数了。

Object.prototype.toString.call(test) // 显示[object GeneratorFunction]

  生成器函数的行为与普通函数并不相同,表现为如下3点:

  1. 通过new运算符或函数调用的形式调用生成器函数,均会返回一个生成器实例;

  2. 通过new运算符或函数调用的形式调用生成器函数,均不会马上执行函数体的代码;

  3. 必须调用生成器实例的next方法才会执行生成器函数体的代码。

function *say(msg){  console.log(msg)}var gen = say(hello world) // 没有显示hello worldconsole.log(Object.prototype.toString.call(gen)) // 显示[object Generator]gen.next() // 显示hello world

  2、 关键字yield——迭代器生成器

   用于马上退出代码块并保留现场,当执行迭代器的next函数时,则能从退出点恢复现场并继续执行下去。下面有2点需要注意:

    1. yield后面的表达式将作为迭代器next函数的返回值;

    2. 迭代器next函数的入参将作为yield的返回值(有点像运算符)。

  3、迭代器(Generator)

    迭代器是一个拥有 {value:{*}, done:{Boolean}} next([*])方法 的对象,通过next函数不断执行以关键字yield分割的代码段

 

三、迭代器                        

   迭代器是迭代器模式的实现,好处是“延迟执行”,通过迭代器我们可以很轻松地实现Python中的rang函数来创建一个庞大的序列。

var RangeIterator = function(start,end,scan){    this.start = arguments.length >= 2 ? start : 0        this.end = end == undefined ? start : end    this.scan = scan || 1    this.idx = this.start}RangeIterator.prototype.next = function(){    if (this.idx > this.end)     if (!!StopIteration) {         throw StopIteration       }else{          return void 0       }    var ret = this.idx    this.idx += this.scan    return ret}var range = function(start, end, scan){   var iterator = new RangeIterator(start, end, scan)   return {        __iterator__: function(){            return iterator        },        next: function(){            return iterator.next()        },        toString: function(){            var array = []            for (var i = this.next(); i != void 0; i = this.next())                array.push(i)            return array + ‘‘        }        }}var r = range(1, 100000000000000000000)// FF下for(var i in r)  console.log(i) // 显示1到99999999999999999999// 所有浏览器for (var i = r.next(); i != void 0; i = r.next())  console.log(i) // 显示1到99999999999999999999

  由于JS是单线程运行,并且当UI线程被阻塞N秒后,浏览器会询问是否停止脚本的执行,但上述代码并不会由于序列过大造成内存溢出的问题。假如预先生成1到99999999999999999999或更大数字的数组,那很有可能造成stack overflow。那是由于迭代器实质为一状态机,而调用next函数则是触发状态的转换,而状态机中同一时刻用于存放变量的存储空间固定,并不会出现无限增长的情况。

 

四、简单反编译yield关键字                    

   回到关键字yield上了,其实yield关键字就是以一种更直观、便捷的方式让我们创建迭代器,而yield则用于分割迭代器不同状态所执行的代码段。下面我们一起简单反编译yield关键字。

// 定义生成器函数function *enumerable(msg){  console.log(msg)  var msg1 = yield msg +   after   console.log(msg1)  var msg2 = yield msg1 +  after  console.log(msg2 +  over)}

  反编译:

var enumerable = function(msg){  var state = -1   return {    next: function(val){      switch(++state){         case 0:                  console.log(msg +  after)                  break         case 1:                  var msg1 = val                  console.log(msg1 +  after)                  break         case 2:                  var msg2 = val                  console.log(msg2 +  over)                  break      }    }  }}

(注意:上述仅仅简单的反编译,更复杂的情况可以参考@赵劼的《人肉反编译使用关键字yield的方法》)

 

 五、异步调用中的应用                       

var iterator = getArticles(dummy.json)// 开始执行iterator.next()// 异步任务模型function getData(src){  setTimeout(function(){    iterator.next({tpl: tpl.html, name: fsjohnhuang})  }, 1000)}function getTpl(tpl){  setTimeout(function(){    iterator.next(hello ${name})  }, 3000)}
// 同步任务function render(data, tpl){
return tpl.replace(/\$\{(\w+)\}/, function(){ return data[arguments[1]] == void 0 ? arguments[0] : data[arguments[1]] })}// 主逻辑function *getAritcles(src){ console.log(begin) var data = http://www.mamicode.com/yield getData(src) var tpl = yield getTpl(data.tpl) var res = render(data, tpl) console.log(rest)}

  主逻辑中异步调用的写法与同步调用的基本没差异了,爽了吧!但异步任务模型与生成器函数及其生成的迭代器耦合性太大,还是不太好用。下面我们通过实现了Promises/A+规范的Q来进一步解耦。

 

六、与Q结合                        

// 异步任务模型function getData(src){  var deferred = Q.defer()  setTimeout(function(){   defer.resolve({tpl: tpl.html, name: fsjohnhuang})  }, 1000)  return deferred.promise}function getTpl(tpl){  var deferred = Q.defer()  setTimeout(function(){   defer.resolve(hello ${name})  }, 3000)  return deferred.promise}// 同步任务function render(data, tpl){  return tpl.replace(/\$\{(\w+)\}/, function(){    return data[arguments[1]] ==  void 0 ? arguments[0] : data[arguments[1]]  })}// 主逻辑Q.async(function *(){  console.log(begin)  var data = http://www.mamicode.com/yield getData(dummy.json)  var tpl = yield getTpl(data.tpl)  var res = render(data, tpl)  console.log(rest)})

 

七、总结                          

  Generator Function并不是为异步编程而生,但可以将它结合Promise来实现良好的异步编程模型。本篇内容仅简单介绍Generator Function及相关的异步编程内容,若有纰漏请各位指正,谢谢!

  尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4166267.html ^_^肥仔John

 

八、 参考                          

http://huangj.in/765

https://www.imququ.com/post/generator-function-in-es6.html

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/The_Iterator_protocol

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*

http://www.cnblogs.com/yangecnu/archive/2012/03/17/2402432.html

http://www.cnblogs.com/draem0507/p/3795189.html

http://blog.zhaojie.me/2010/06/code-for-fun-iterator-generator-yield-in-javascript-answer-2-loop-and-interpreter.html

http://blog.zhaojie.me/2010/06/code-for-fun-iterator-generator-yield-in-javascript.html

http://blog.zhaojie.me/2010/07/why-java-sucks-and-csharp-rocks-6-yield.html

JS魔法堂:ES6新特性——GeneratorFunction介绍