资讯专栏INFORMATION COLUMN

es6 generator函数

voidking / 1069人阅读

摘要:返回的遍历器对象可以依次遍历函数内部的每一个状态。示例内部捕获外部捕获内部捕获外部捕获上面代码遍历器对象连续抛出两个错误,第一个被函数体内的捕获。上面代码中,首先执行函数,获取遍历器对象,然后使用方法第二行,执行异步任务的第一阶段。

参考 来源《ecmascript6 入门》generator部分

认识generator函数

形式上,generator函数有两个特点:一是function关键字与函数名之间有一个*。二是函数体内使用yield语句,如下代码。(yield在英语中意思就是 产出)

function* helloWorld(){
    yield ‘hello’;
    yield ‘world’;
    return ‘ending’;
}

var hw=helloWorld();

调用执行,调用generator函数和调用普通函数的形式一样,没有区别,比如上面helloWorld()
但是内部的执行与普通函数是完全不同,调用generator函数之后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象。也就是说generator函数还是一个遍历器对象生成函数。返回的遍历器对象可以依次遍历generator函数内部的每一个状态。

它是怎么遍历的呢?。遍历器对象每次调用next方法,内部指针就从函数头部或者上一次停下来的的地方开始执行,遇到yield语句暂停并返回一个对象,下一次调用next,遇到下一个yield暂停并返回一个对象(对象拥有value,和done属性)。value的值就是yield语句的值,done属性表示遍历是否结束(false没有结束,true结束)。

上面示例代码用调用4次next:

第一次调用next,generator函数开始执行,遇到第一个yield暂停,并且返回一个对象,value =hello,done=false表示还遍历还没有结束。

第二次调用next,从上次暂停的位置开始执行,遇到下一个yield暂停,并返回一个对象。。

第三次调用next,返回value为return的值,done为true表示遍历结束。

第四次调用next,generator函数已经运行完毕,返回value为undefined,done为true。

yield和return;

yield语句与return语句既有相似之处 ,也有区别。相似之处在于都可以返回紧跟在其后边的表达式的值。区别在于每次遇到yield,函数暂停,下一次在从该位置向后执行,而return语句没有此位置记忆功能,一个函数里面只能执行一次,而yield正因为可以有多个,可以返回多个值,所以generator函数可以返回一系列的值,这也就是它名称的来历(generator英语意思为生成器)

与Iterator接口的关系

任意一个对象的iterator接口都是部署在了Symbol.iterator属性,由于generator函数就是遍历器生成函数,所以可以直接把它赋值给Symbol.iterator,从而使的该对象具有Iterator接口。

示例:

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

说明:代码中generator函数赋给了myIterable对象的Symbol.iterator属性,使的该对象具有iterator接口,可以 被()运算符遍历。为什么是这样?(…)三个点这里叫做扩展运算符,它的执行是调用了遍历器方法(它可以将一个数组转为用逗号分割的序列,可以用于函数调用传参),这里就是generator函数,然后返回一个遍历器对象,然后重复调用它的next方法。其实不只有扩展运算符,for..of循环的执行也是调用的iterator接口方法,也就是说只有部署了iterator接口的数据集合才可以使用for...of,扩展运算符遍历。

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

上面代码遍历器对象连续抛出两个错误,第一个被generator函数体内的catch捕获。第二个由于generator函数体内的catch已经执行过了,所以被外面的catch捕获。如果generator函数体内没有try...catch...语句,那么就会被外面的catch语句捕获。如果都没有try...catch...,那么程序报错。

5.Generator.prototype.return()

yield*语句

如果在 Generator 函数内部,调用另一个 Generator 函数,默认情况下是没有效果的。yield*语句可以用来在一个 Generator 函数里面执行另一个 Generator 函数。

function* foo() {
  yield "a";
  yield "b";
}

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"

从语法角度看,如果yield命令后面跟的是一个遍历器对象,需要在yield命令后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*语句。

let delegatedIterator = (function* () {
  yield "Hello!";
  yield "Bye!";
}());

let delegatingIterator = (function* () {
  yield "Greetings!";
  yield* delegatedIterator;
  yield "Ok, bye.";
}());

for(let value of delegatingIterator) {
  console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye.”

yield*后面的Generator函数(没有return语句时),等同于在Generator函数内部,部署一个for...of循环。

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;
  }
}

上面代码,yield* 执行的是一个遍历器,for...of...循环的也是一个遍历器,所以for...of...返回yield value时等同于yield*

两个平常会用到的示例:

1)遍历嵌套的数组:

function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for(let i=0; i < tree.length; i++) {
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree;
  }
}

const tree = [ "a", ["b", "c"], ["d", "e"] ];

for(let x of iterTree(tree)) {
  console.log(x);
}
// a
// b
// c
// d
// e

2)对于状态的控制:

var clock = function*() {
  while (true) {
    console.log("Tick!");
    yield;
    console.log("Tock!");
    yield;
  }
};
作为对象属性的generator

如果一个对象的属性是**Generator**函数,可以简写成下面的形式

let obj = {
  * myGeneratorMethod() {
    ···
  }
};

等同于

let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};
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对象。所以如果在generator函数内使用this,obj对象访问不到。

那么,有没有办法让Generator函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
var f = F.call(F.prototype);

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

上面代码:首先使用call函数将F函数的this绑定到F.prototype;而f还是那个遍历器对象是F函数的实例,又可以继承F.prototype的属性,所以也就可以访问F.prototype代表的this的属性了。

Generator函数的应用

generator函数最大的作用可以用作异步任务的封装(由于它的yield命令特性,可以暂停和恢复执行)。而之前javascript对于异步的实现主要就是 回调函数,事件监听,promise等。

示例:

var fetch = require("node-fetch");

function* gen(){
  var url = "https://api.github.com/users/github";
  var result = yield fetch(url);
  console.log(result.bio);
}

上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。就像前面说过的,这段代码非常像同步操作,除了加上了yield命令。

var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

上面代码中,首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。由于Fetch模块返回的是一个 Promise 对象,而这个对象被yield返回到了它的value属性中,因此要用.value.then方法调用then方法。成功后 return数据参数data可以被第二个then方法中接受。而第二次调用then方法传入的data又传回了gen函数给了变量result。value往出传值,next可以往里传值。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82147.html

相关文章

  • ES6学习笔记之Generator函数

    摘要:调用函数后和普通函数不同的是,该函数并不立即执行,也不返回函数执行结果,而是返回一个指向内部状态的对象,也可以看作是一个遍历器对象。第一个只是用来启动函数内部的遍历器,传参也没有多大意义。 之前断断续续接触到了一些ES6的知识,异步编程方面听得比较多的就是Promise,直到最近比较系统地学习了ES6的新特性才发现Generator这个神奇的存在,它可以实现一些前所未有的事情,让我顿时...

    cjie 评论0 收藏0
  • 深入理解 Generator 函数

    摘要:同时,迭代器有一个方法来向函数中暂停处抛出一个错误,该错误依然可以通过函数内部的模块进行捕获处理。 本文翻译自:Diving Deeper With ES6 Generators 由于个人能力有限,翻译中难免有纰漏和错误,望不吝指正issue ES6 Generators:完整系列 The Basics Of ES6 Generators Diving Deeper With E...

    jzzlee 评论0 收藏0
  • 关于协程和 ES6 中的 Generator

    摘要:关于协程和中的什么是协程进程和线程众所周知,进程和线程都是一个时间段的描述,是工作时间段的描述,不过是颗粒大小不同,进程是资源分配的最小单位,线程是调度的最小单位。子程序就是协程的一种特例。 关于协程和 ES6 中的 Generator 什么是协程? 进程和线程 众所周知,进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同,进程是 CPU 资源分配的最小单位,...

    RyanHoo 评论0 收藏0
  • 【Web全栈课程三】ES6特性介绍(下)

    摘要:示例运行函数弹出弹出函数接收参数,返回值。其中,返回一个对象,是的返回值,代表函数是否执行完成。 ES6特性介绍(下) ES6新的标准,新的语法特征:1、变量/赋值2、函数3、数组/json4、字符串5、面向对象6、Promise7、generator8、ES7:async/await 《【Web全栈课程二】ES6特性介绍(上)》见:https://segmentfault.com/a...

    wangshijun 评论0 收藏0
  • ES6新特性 iterators and Generators

    摘要:在函数定义上使用关键字来表示方法调用时返回的值。是一个有属性的。这个指向一个函数,这个函数返回关于这个对象的。在中所有的集合类对象和字符串都是,并且有自己默认的。注意本身是不返回任何值的,它只向外部产生值。 ES6新特性 iterators and Generators ES6中引入了许多新特性,目前大量的JavaScript项目已经使用了ES6来进行开发,那么熟悉这些新的特性是十分必...

    pf_miles 评论0 收藏0
  • ES6中的异步编程:Generators函数(一)

    摘要:由于可以使用语句来暂停异步操作,这让异步编程的代码,很像同步数据流方法一样。该临时函数就叫做函数。下面就是简单的函数转换器。 访问原文地址 对ES6的generators的介绍分为3个部分 第一部分base介绍及使用 第二部分基于generators和Promise实现最强大的异步处理逻辑 概述 Generator函数是协程在ES6的实现,用来做异步流程的封装,最大特点就是可以交出...

    ztyzz 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<