Generator函数的语法

2019-08-22 13:35:27

参考文章 Generator函数的语法

1. 简介

Generator函数是es6提供的一种异步编程的解决方案,语法行为与传统函数完全不一样。

Generator函数有多种理解角度,从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。

执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了是状态机还是一个遍历器对象生成函数。 返回遍历器对象,可以依次遍历Generator函数内部的每一个状态。

形式上Generator函数是一个普通函数,但是有两个特征。  一是,function关键字与函数之间有一个 * ,二是,函数体内部使用yield表达式,定义不同的内部状态。

function* gen(){    yield "hello";    yield "world";    return "ends"}let g1=gen()

上面代码定义了一个Generator函数gen,它内部有两个yield表达式("hello"和"world"),即该函数有三个状态,hello,world和return语句。

Generator函数的调用和普通函数的调用一样,也是在后面加上圆括号。

不同的是,调用Generator函数后,函数并不执行,返回的也不是函数运行结果,而是指向内部应用状态的指针对象,就是遍历器对象。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或者上次停下的地方开始执行,直到遇到下一个yield表达式(或return语句为止)。

换言之,Generator函数是分段执行的。yield表达式只是暂停的标志,而next方法可以恢复执行。

function* gen(){    yield "hello";    yield "world";    return "ends"}let g1=gen()console.log(g1.next());    //  {value:'hello',done:false}console.log(g1.next())     //  {value:"world",done:false}console.log(g1.next())     //  {value:"ends",done:true}console.log(g1.next())     // {value:undefined,done:true}console.log(g1.next())     // {value:undefined,done:true}

第一次调用,Generator函数开始执行,直到遇到第一个yield表达式为止。 next方法返回一个对象,它的value属性就是当前yield表达式的值hellodone属性值false,表示遍历还没结束。

第二次调用,Generator函数从上一次yield表达式停下来的地方,一直执行到下一个yield表达式。 next方法返回的对象的value属性就是当前yield表达式的值worlddone属性值false表示当前遍历还没结束。

第三次调用,Generator函数从上一次yield表达式停下来的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next返回的对象的value属性,就是紧跟在return语句后面的表达式的值如果没有return语句,则属性的值为undefined

以后再调用这个方法,返回的都是这个值。

总结一下,调用Generator函数,返回的都是一个遍历器对象,代表Generator函数内部指针。  以后每次调用Generator函数的next方法,就会返回一个有着valuedone两个属性的对象。 value属性表示当前内部状态的值,是yield表达式后面那个表达式的值done属性值是一个布尔值,表示当前遍历是否结束。

es6没有规定,function关键字与函数名之间的星号,写在哪个位置,这导致下面的写法都能通过。

function * g1(){}function *g1(){}function* g1(){}function*g1(){}

我们一般采用第三种写法。


  1. yield表达式



  由于Generator函数返回的是遍历器对象,只有调用next方法才会遍历下一个内部状态,所以实际提供了一个可以暂停执行的函数。

yield表达式就是暂停的标志。

遍历器对象的next方法运行逻辑如下。

(1) 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value值。

(2)下一次再调用next方法时,再继续往下执行,直到再遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return表达式后面的值,作为返回对象的value属性值。

(4)如果该对象没有return语句,则返回对象的value属性值为undefined

需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时,才会执行。 因此等于说是为javascript提供了惰性求值的语法功能。

function* gen(){    yield 123+456}let g1=gen()console.log(g1.next())               //    {value:579,done:false}

上面代码中,yield后面的表达式123+456,不会立即求职,只会在next方法指针移到这一句话时,才会求职。

yield表达式与return语句既有相似之处,又有区别。 相似之处在于,都能返回紧跟在语句后面那个表达式的值。  不同之处在于,每次遇到yield,函数暂停执行,下次再从该位置继续往后执行。 而return语句不具备位置记忆功能。   一个函数里面,只能执行一次(或者说只能有1个return语句),但是可以执行多次yield表达式。   正常函数只能返回一个值,因为只能执行一次return, Generator函数可以返回一系列的值,因为可以有任意多个yield

从另一个角度看,也可以说是Generator生成一些列的值。这就是它名称的由来,yield在英文意思中表示"产出"。

Generator函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。

function* gen(){    console.log("执行了!")
}let g1=gen()console.log(g1);   //  gen {<suspended>}

上面代码中,如果函数gen是一个普通函数的话,那么调用的时候就会立即执行,打印"执行了!",但是作为一个Generator函数,调用的时候他是不会执行的,只有在你调用了next方法后才会执行。


function* gen(){    console.log("执行了!");  //  执行了!}let g1=gen()console.log(g1.next());   //  {value:undefined,done:true}

只有在调用了next方法后才会执行。

另外需要注意,yield表达式只能用在Generator函数里面,用在普通的函数里面会报错。

function f(){    yield 123}   //  Uncaught SyntaxError: Unexpected number


另外,yield表达式如果在另一个表达式中,必须放上圆括号。

function* demo() {  console.log('Hello' + yield); // SyntaxError
  console.log('Hello' + yield 123); // SyntaxError

  console.log('Hello' + (yield)); // OK
  console.log('Hello' + (yield 123)); // OK}

yield表达式用作函数参数,或者放在表达式的右边,可以不加括号。

function* demo() {
  foo(yield 'a', yield 'b'); // OK
  let input = yield; // OK}



3.与iterator接口的关系

任意一个对象的 Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。

由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口。

// 正常情况下,object是不能被for...of被遍历的var obj={        name:"Andy",        age:30,        salary:1000}for(let i of obj){    console.log(i);
}

obj

当尝试使用for...of 去遍历object时,控制会报错, obj is not iterable   对象不能被遍历

下面我们尝试为对象部署iterator接口

var obj={}

obj[Symbol.iterator]=function* (){    yield 1;    yield 2;    yield 3;
}for(let i of obj){    console.log(i);
}

现在这个obj对象可以被for...of 遍历了

iterator



代码做点小修改,使得obj 可以被for...of遍历,控制台打印obj的value

var obj={name:"Andy",age:30,weight:180}

obj[Symbol.iterator]=function* (){    //  Object.keys()方法返回的是一个数组,数组中的元素是对象所有的key
    var key=Object.keys(obj);  //  ["name","age","weight"]

    for(let i=0;i<key.length;i++){        yield key[i];
    }
    
}for(let i of obj){    console.log(obj[i])
}//  chrome控制台打印//  Andy//  30//  180



2.  next方法的参数

yield表达式本身没有返回值,或者说总是返回undefined。 next方法可以带一个参数,该参数就会被当做上一个yield表达式的返回值。

function* gen(){    for(let i=0;i<5;i++){        let reset=yield i;        console.log(reset)
    }

}var g=gen();

g.next()  

g.next()   //  undefined      gen.js:5 提示第5行打印

上面代码中,Generator函数gen,将表达式 yield i赋值给reset,控制台打印reset,显示的是undefined。

如果next方法没有带参数,每次运行到yield表达式变量reset的值总是undefined。

下面我们尝试将next方法带参数,再来看看控制台打印结果。

function* gen(){    for(let i=0;i<5;i++){        var reset=yield i;        console.log(reset)
    }

}var g=gen();

g.next(123)  

g.next(456)   //  456      gen.js:5 提示第5行打印

上面代码中,第一次调用g.next()方法后,代码运行到 var reset=yield i  这条语句,因此没有打印值。

下次再调用 g.next() 方法后,由于next方法携带了参数456,因此456就作为了yield表达式的返回值。

这个功能有很重要的语法意义。    Generator函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。  通过next方法的参数,就有办法再Generator函数开始运行之后,继续向函数体内部注入值。  也就是说,可以在Generator函数运行的不同阶段,从外部向内部注入值,从而调整函数行为。




  • 2019-03-26 19:29:05

    Android NDK开发Crash错误定位

     在Android开发中,程序Crash分三种情况:未捕获的异常、ANR(Application Not Responding)和闪退(NDK引发错误)。其中未捕获的异常根据logcat打印的堆栈信息很容易定位错误。ANR错误也好查,Android规定,应用与用户进行交互时,如果5秒内没有响应用户的操作,则会引发ANR错误,并弹出一个系统提示框,让用户选择继续等待或立即关闭程序。并会在/data/anr目录下生成一个traces.txt文件,记录系统产生anr异常的堆栈和线程信息。如果是闪退,这问题比较难查, --------------------- 作者:xyang0917 来源:CSDN 原文:https://blog.csdn.net/xyang81/article/details/42319789 版权声明:本文为博主原创文章,转载请附上博文链接!

  • 2019-04-01 22:46:39

    电子签章的实施方案

    WORD/EXCEL签章模块,该部分实现与WORD/EXCEL的无缝结合,并提供给用户简单直观的菜单和工具条来实现文档签章验证等各种操作,其中,KHSC-64智能密码钥匙是签章模块中用户证书和图章的载体

  • 2019-04-01 22:48:25

    如何用 Java 对 PDF 文件进行电子签章

    印章是我国特有的历史文化产物,古代主要用作身份凭证和行驶职权的工具。它的起源是由于社会生活的实际需要。早在商周时代,印章就已经产生。如今的印章已成为一种独特的,融实用性和艺术性为一体的艺术瑰宝。传统的印章容易被坏人、小人私刻;从而新闻鲜有报道某某私刻公章,侵吞国家财产。随着计算机技术、加密技术及图像处理技术的发展,出现了电子签章。电子签章是电子签名的一种表现形式,利用图像处理技术、数字加密技术将电子签名操作转化为与纸质文件盖章操作相同的可视效果,同时利用电子签名技术保障电子信息的真实性和完整性以及签名人的不可否认性

  • 2019-04-01 22:59:22

    Android Studio 3.0 利用cmake搭建jni环境(很详细哦)

    我用的Android Studio是3.0的版本,然后想搭建一下jni的环境。这里把自己遇到的问题和注意点都记录下。 首先是需要在android studio里面安装最基本的环境。 打开Default Preference里面查看SDK Tool选项。

  • 2019-04-01 23:20:58

    隐藏在图片中的密钥

    在客户端开发的时候,有时需要把密钥保存在本地。这时就会遇到密钥安全性的问题。要保证密钥安全性,无非就是混淆、隐藏、白盒等手段。本文以隐藏在图片中来阐述密钥的安全保存。

  • 2019-04-01 23:22:33

    验证数字签名

    应预先设置验证首选项。在打开 PDF 后显示包含签名的验证详细信息时,这有助于确保数字签名有效。有关详细信息,请参阅设置签名验证首选项。

  • 2019-04-01 23:23:27

    图像隐写之使用PHP隐藏图像中的文本

    隐写术是一门研究隐藏信息的科学艺术,通过隐写术,可以只让发送者和接收者知道隐藏的信息。 图像隐写术则是研究将数据隐藏在图像中,通过该技术可以防止无关用户发现这些隐藏的信息或数据。