介绍ES6 Generators

什么是Generators(生成器函数)?让我们先来看看一个例子。

function* quips(name) {
yield "hello " + name + "!";
yield "i hope you are enjoying the blog posts";
if (name.startsWith("X")) {
yield "it's cool how your name starts with X, " + name;
}
yield "see you later!";
}

这是一只会说话的猫的一些代码,可能是当今互联网上最重要的一种应用。它看起来有点像一个函数,对吗?这被称为生成器-函数,它与函数有很多共同之处。但你马上就能看到两个不同之处。

  • 普通函数以function开头,生成器函数以function*开头
  • 在生成器函数中,yield是一个关键字,语法看起来像return。不同的是,函数(甚至是生成器函数)只能返回一次,而生成器函数可以“yield”任何次数。yield表达式暂停了生成器的执行,同时它可以在以后再次恢复。

Generators可以做什么

当你调用生成器-函数quips()时会发生什么?

> var iter = quips("jorendorff");
[object Generator]
> iter.next()
{ value: "hello jorendorff!", done: false }
> iter.next()
{ value: "i hope you are enjoying the blog posts", done: false }
> iter.next()
{ value: "see you later!", done: false }
> iter.next()
{ value: undefined, done: true }

你可能已经非常习惯于普通函数和它们的行为方式。当你调用它们时,它们会立即开始运行,并一直运行到返回或抛出异常。所有这些对任何JS程序员来说都是第二天性。调用一个生成器看起来也是一样的:quips("jorendorff")。但是当你调用一个生成器时,它还没有开始运行。相反,它返回一个暂停的Generator对象iter(就是在上面的例子中叫做iter的对象)。你可以把这个Generator对象看作是一个函数调用,在调用前被冻结。具体来说,它被冻结在生成器函数的顶端,就在运行其第一行代码之前。每次你调用Generator对象的方法.next()时,函数调用都会自我解冻,并运行到下一个yield表达式为止。这就是为什么我们每次调用上面的iter.next()方法,都会得到一个不同的字符串值。这些都是由函数quips()中的yield表达式产生的值。在最后一次iter.next()调用中,我们终于到达了生成器-函数的终点,所以结果中.done字段的值是true。到达生成器函数的终点就像普通函数返回undefined一样,这就是为什么结果的value字段值是undefined

现在可能是一个好时机,回到会说话的猫的演示页面,真正地玩一玩代码。试着把yield放在一个循环里面。会发生什么?从技术上讲,每次Generator执行yield时,它的堆栈--局部变量、参数、临时值以及当前在Generator主体中的执行位置--都会从堆栈中删除。然而,Generator对象会保留对这个堆栈框架的引用(或副本),以便以后.next()调用可以重新激活它并继续执行。

值得指出的是,Generator不是线程。在有线程的语言中,多段代码可以同时运行,通常会导致竞赛条件、非确定性和甜蜜的性能。Generator则完全不是这样的。当一个Generator运行时,它与调用者在同一个线程中运行。执行的顺序是顺序的、确定的,而不是并发的。与系统线程不同,Generator只在其函数体中标明的yield点上暂停运行。

好了。我们知道Generator是什么。我们已经看到了一个Generator的运行,暂停自己,然后恢复执行。现在有个大问题。这种奇怪的能力怎么可能有用?

Generators就是迭代器(Generators are iterators)

ES6迭代器不仅仅是一个单一的内置类。它们是该语言的一个扩展点。你可以通过实现两个方法Symbol.iteratornext()来创建你自己的迭代器。但是实现一个接口至少要做一点工作。让我们看看迭代器的实现在实践中是什么样的。作为一个例子,让我们做一个简单的迭代器range,它只是从一个数字到另一个数字进行计数,就像一个老式的C循环for (;一样。

// This should "ding" three times
for (var value of range(0, 3)) {
alert("Ding! at floor #" + value);
}

这里有一个解决方案,使用ES6类class

class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
} [Symbol.iterator]() { return this; } next() {
var value = this.value;
if (value < this.stop) {
this.value++;
return {done: false, value: value};
} else {
return {done: true, value: undefined};
}
}
} // Return a new iterator that counts up from 'start' to 'stop'.
function range(start, stop) {
return new RangeIterator(start, stop);
}

代码示例

这就是在Java或Swift中实现迭代器的情况。这并不坏。但也不完全是微不足道的。这段代码里有什么错误吗?这可不好说。它看起来完全不像我们在这里试图模仿的原始循环:for (;,迭代器协议迫使我们拆除了循环。在这一点上,你可能对迭代器感到有点冷淡。它们可能很好用,但似乎很难实现。

你可能不会想建议我们在JS语言中引入一个疯狂的、令人费解的新控制流结构,只是为了使迭代器更容易构建。但既然我们有生成器Generator,我们能在这里使用它们吗?让我们试试吧。

function* range(start, stop) {
for (var i = start; i < stop; i++)
yield i;
}

代码示例

上面的4行range()代码可以直接替代以前的23行实现,包括整个类RangeIterator就是因为Generator是迭代器,所以这一切才是可能的。所有的生成器都有一个内置的next()Symbol.iterator的实现。 你只需写出循环的行为。

在没有Generator的情况下实现迭代器,就像被迫完全用被动语态来写一封长邮件。当简单地说出你的意思不是一个选项时,你最终说的东西可能会变得相当复杂。"我的意思是,我的意思是,我必须在不使用循环语法的情况下描述一个循环的功能,所以RangeIterator又长又奇怪。而Generator就是答案

我们还可以如何利用生成器作为迭代器的能力呢?

  • 让任何对象都可以迭代。只需写一个Generator函数来遍历this,在遍历时产生(yield)每个值。然后把这个生成器函数设置为this对象的[Symbol.iterator]方法。
  • 简化建数组函数。假设你有一个函数,每次调用都会返回一个数组的结果,就像下面这个函数:
//将一维数组'图标'切分成长度为'rowLength'的数组
function splitIntoRows(icons, rowLength) {
var rows = [];
for (var i = 0; i < icons.length; i += rowLength) {
rows.push(icons.slice(i, i + rowLength));
}
return rows;
}

使用Generator会让这种代码更短一些。

function* splitIntoRows(icons, rowLength) {
for (var i = 0; i < icons.length; i += rowLength) {
yield icons.slice(i, i + rowLength);
}
}

执行时唯一区别是,它不是一次性计算所有的结果并返回一个数组,而是返回一个迭代器,然后根据需要逐个计算结果。

  • 异常大小的结果。你不可能建立一个无限的数组。但是你可以返回一个Generator,生成一个无尽的序列,每个调用者可以从其中提取他们需要的任何数量的值。

  • 重构复杂的循环。你有一个巨大的丑陋的函数吗?你想把它分解成两个更简单的部分吗?Generator是添加到你的重构工具箱中的一把新刀。当你面对一个复杂的循环时,你可以把代码中产生数据的部分分解出来,把它变成一个单独的生成器-函数。然后将循环改为:for (var data of myNewGenerator(args))

  • 处理可迭代数据的工具。ES6并没有提供一个扩展库,用于过滤、映射,以及一般情况下对任意的可迭代数据集进行任意的处理。但是Generator对于构建你所需要的工具来说是非常棒的,只需要几行代码。例如,假设你需要一个新的在DOM NodeLists上遍历的方法,而不仅仅是Arrays。小菜一碟:创建Array.prototype.filter

function* filter(test, iterable) {
for (var item of iterable) {
if (test(item))
yield item;
}
}

那么Generator是否有用呢?当然,它们是实现自定义迭代器的一种惊人的简单方法,而且迭代器是整个ES6的数据和循环的新标准。但这并不是Generator的全部功能。这甚至可能不是它们所做的最重要的事情。

生成器与异步代码(Generators and asynchronous code)

下面是我前段时间写的一些JS代码。

          };
})
});
});
});
});

也许你已经在自己的代码中看到了这样的东西。异步API通常需要一个回调,这意味着每次你做什么都要写一个额外的匿名函数。因此,如果你有一点代码做三件事,而不是三行代码,你就会看到三个缩进层次的代码。下面是我写的一些更多的JS代码。

}).on('close', function () {
done(undefined, undefined);
}).on('error', function (error) {
done(error);
});

异步API有错误处理惯例,而不是异常。不同的API有不同的约定。在大多数API中,默认情况下,错误会被默默地放弃。在一些API中,即使是普通的成功完成也是默认放弃的。直到现在,这些问题都是我们为异步编程付出的代价。我们已经接受了这样的事实:异步代码看起来并不像相应的同步代码那样漂亮和简单。

Generator提供了新的希望:我们不必再写那样丑陋的代码。

Q.async()是一个实验性的尝试,它使用Generators与Promises来产生类似于相应同步代码的异步代码。比如说:

// Synchronous code to make some noise.
function makeNoise() {
shake();
rattle();
roll();
} // Asynchronous code to make some noise.
// Returns a Promise object that becomes resolved
// when we're done making noise.
function makeNoise_async() {
return Q.async(function* () {
yield shake_async();
yield rattle_async();
yield roll_async();
});
}

主要的区别是,异步版本必须在调用异步函数的每个地方添加关键字yield。在该版本中添加像语句if或try/catch块这样的代码,就像在普通的同步版本中添加它一样。与其他编写异步代码的方式相比,这感觉不像是在学习一种全新的语言。(延伸阅读

因此,Generator为一种新的异步编程模型指明了方向,它似乎更适合人类的大脑。这项工作正在进行中。在其他方面,更好的语法可能会有帮助。一项关于异步函数的建议,建立在Promises和Generators的基础上,并从C#的类似功能中获得灵感,已被提上ES7的议程

ES6深度解析3:Generators的更多相关文章

  1. [ES6深度解析]12:Classes

    我们将讨论一个老问题:在JavaScript中创建对象的构造函数. 存在的问题 假设我们想要创建最典型的面向对象设计的示例:Circle类.假设我们正在为一个简单的Canvas库编写一个Circle. ...

  2. [ES6深度解析]15:模块 Module

    JavaScript项目已经发展到令人瞠目结舌的规模,社区已经开发了用于大规模工作的工具.你需要的最基本的东西之一是一个模块系统,这是一种将你的工作分散到多个文件和目录的方法--但仍然要确保你的所有代 ...

  3. [ES6深度解析]13:let const

    当Brendan Eich在1995年设计了JavaScript的第一个版本时,他犯了很多错误,包括从那时起就成为该语言一部分的一些错误,比如Date对象和当你不小心将它们相乘时对象会自动转换为NaN ...

  4. [ES6深度解析]14:子类 Subclassing

    我们描述了ES6中添加的新类系统,用于处理创建对象构造函数的琐碎情况.我们展示了如何使用它来编写如下代码: class Circle { constructor(radius) { this.radi ...

  5. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  6. 【进阶3-5期】深度解析 new 原理及模拟实现(转)

    这是我在公众号(高级前端进阶)看到的文章,现在做笔记 https://github.com/yygmind/blog/issues/24 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的 ...

  7. [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析

    [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...

  8. 第37课 深度解析QMap与QHash

    1. QMap深度解析 (1)QMap是一个以升序键顺序存储键值对的数据结构 ①QMap原型为 class QMap<K, T>模板 ②QMap中的键值对根据Key进行了排序 ③QMap中 ...

  9. Deep Learning模型之:CNN卷积神经网络(一)深度解析CNN

    http://m.blog.csdn.net/blog/wu010555688/24487301 本文整理了网上几位大牛的博客,详细地讲解了CNN的基础结构与核心思想,欢迎交流. [1]Deep le ...

随机推荐

  1. 1.7 Systemd初始化进程

    1.7 Systemd初始化进程 Linux操作系统的开机过程是这样的,即从BIOS开始,然后进入Boot Loader,再加载系统内核,然后内核进行初始化,最后启动初始化进程.初始化进程作为Linu ...

  2. tar -zxvf file.tar.gz //解压tar.gz

    http://apps.hi.baidu.com/share/detail/37384818 download ADT link http://dl.google.com/android/ADT-0. ...

  3. MyBatis 各种参数传递方式

    MyBatis参数传递方式 情况一:Mapper映射器接口方法参数只有一个且为基本类型 接口方法: public List<UserEntity> selectUserByAge(int ...

  4. Canvas跟随鼠标炫彩小球

    跟随鼠标炫彩小球 canvas没有让我失望,真的很有意思 实现效果 超级炫酷 实现原理 创建小球 给小球添加随机颜色,随机半径 鼠标移动通过实例化,新增小球 通过调用给原型新增的方法,来实现小球的动画 ...

  5. 修改mysql中数据库存储主路径

    一.首先把mysql的服务先停掉. 二.更改MySQL配置文件My.ini中的数据库存储主路径 打开文件夹C:\ProgramData\MySQL\MySQL Server 5.7中的my.ini文件 ...

  6. Nextcloud 使用教程

    一.简介 Nextcloud是一个网盘式文件管理系统,多用户权限管理,多客户端,使用简单.可在浏览器中运行,也可下客户端,不论使用哪种方式运行,使用教程都是一样的. 只是在客户端中运行时能及时收到相应 ...

  7. DMS是临时解决方案?

    DMS是临时解决方案? Who Says DMS Is an Interim Solution? 现在是认真对待DMS驱动程序监控系统的时候了. 特斯拉(Tesla)在台湾高速公路上撞上翻倒卡车的镜头 ...

  8. 24GHz和77GHz毫米波雷达技术细节

    24GHz和77GHz毫米波雷达技术细节 FMCW Radar Sensitivity Measurement Tech Field Test and Raw Data Analysis Capabi ...

  9. java 全端开源 电商系统 springboot uniapp 小程序 前后端分离 高可用

    Lilishop B2B2C商城系统 官方公众号 & 开源不易,如有帮助请点Star 所有jar包均可从maven中央仓库下载,无二次封装jar包,全端开源,无后门,无监控. 介绍 官网:ht ...

  10. Java synchronized对象级别与类级别的同步锁

    Java synchronized 关键字 可以将一个代码块或一个方法标记为同步代码块.同步代码块是指同一时间只能有一个线程执行的代码,并且执行该代码的线程持有同步锁.synchronized关键字可 ...