阅读更多

3顶
1踩

Web前端

原创新闻 【探秘ES6】系列专栏:生成器

2015-06-17 11:12 by 副主编 mengyidan1988 评论(1) 有4844人浏览
ES6作为新一代JavaScript标准,即将与广大前端开发者见面。为了让大家对ES6的诸多新特性有更深入的了解,Mozilla Web开发者博客推出了《ES6 In Depth》系列文章。CSDN已获授权,将持续对该系列进行翻译,供大家学习借鉴。本文为该系列的第三篇。

ES6生成器介绍

什么是生成器呢?

请先看看以下代码。
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!";
}

这是一段有关汤姆猫(talking cat)的代码。它看上去像是一个函数,是吗?在ES6中,它的名字是生成器函数,其与普通函数有很多相似的地方。但有两点不同:
  • 生成器函数以function*开头;
  • 在生成器函数中,yield是一个关键字,如同return。yield可以多次使用,作用是中断生成器,
  • 而在需要的时候可以恢复生成器的执行。

所以生成器函数最大的特点是可以中断自己,但普通函数不可以。

生成器的作用

当使用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 }

对于普通quips(),它会马上执行直到出现返回或异常抛出等情况。在生成器函数中,调用方式是类似的:quips("jorendorff"),但是它不会马上执行。取而代之的是,它会返回一个已暂停的生成器对象(如上述代码的iter)。你可以把生成器对象看成是一个被暂停的函数调用。要特别说明的是生成器对象在生成器函数开始时被冻结,即第一行代码执行之前。

每当调用生成器对象的.next()方法时,函数恢复运行直至遇到下一个yield表达式,其作用是用于迭代。因此iter.next()的目的是为了返回不同的字符串。在最后的iter.next()中,使用done:true表示结束。到达函数末端意味着返回的结果是undefined,所以代码片段中使用value: undefined结尾。

从技术角度来看,每当生成器执行yield操作时,它的堆栈帧包括本地变量、参数、临时值等都会从堆中被移出。但是生成器对象会保留(拷贝)对该帧的引用,所以.next()可以重新激活它然后继续执行。这里特别要说明的是,生成器不是线程。当一个生成器执行时,它与其调用者都处于同一个线程,是按次序执行而不是并行运行。

可见生成器的作用是暂停本身的运行,然后恢复并继续执行,那么这究竟有何用处呢?

生成器就是迭代器

ES6迭代器不是内建的,可尝试通过使用[Symbol.iterator]()和.next()来进行创建。但是这种类似接口的做法不是最简便的方法。请看下面一个range迭代器例子,它的作用类似于C的for(;;)循环。
// This should "ding" three times
for (var value of range(0, 3)) {
  alert("Ding! at floor #" + value);
}

具体的实现代码:
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);
}

要查看运行情况请点击这里

可见迭代器的生成并不是件简单的事情。那么如果采用生成器来实现,应该如何编写呢?
function* range(start, stop) {
  for (var i = start; i < stop; i++)
    yield i;
}

要查看运行情况请点击这里

对比是很明显的,上述代码仅需4行代码就完成了相同的功能,因为生成器就是迭代器。所有生成器都内建了对.next()和[Symbol.iterator]()的支持,你只需要负责循环的实现就可以了。

除此以外,作为迭代器使用的生成器还可以实现哪些功能呢?

  • 使任何对象可迭代。方法是编写一个生成器函数,然后对每个值进行迭代。然后使用对象的[Symbol.iterator]方法与生成器函数进行绑定。
  • 简化数组功能。如果要实现以数组形式返回函数结果,可以这样写:

// Divide the one-dimensional array 'icons'
// into arrays of length '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;
}

如果使用生成器编写,可以把代码简化为:
function* splitIntoRows(icons, rowLength) {
  for (var i = 0; i < icons.length; i += rowLength) {
    yield icons.slice(i, i + rowLength);
  }
}

后者与前者的区别是不是一次就计算所有结果并返回一个数组,而是先返回一个迭代器,然后按次序按需进行计算。
  • 返回特殊长度数组。数组是有长度限制的,但是透过生成器迭代特性,可以产生无限的序列。
  • 重构复合循环。当编写一个复杂的循环时,可以提出产生数据的部分,把它改写为一个独立的生成器函数。例如(var data of myNewGenerator(args))。
  • 进行迭代运算的配套工具。ES6并没有提供有关筛选、映射、迭代数据集操作的扩展库。但是借助生成器,我们可以围绕它来简单地创建相关工具。

例如,要在DOM节点上实现与Array.prototype.filter类似的功能,可以这样编写:
function* filter(test, iterable) {
  for (var item of iterable) {
    if (test(item))
      yield item;
  }
}

可见,生成器真的妙不可言。借助生成器可以方便地实现定制的迭代操作,而迭代是ES6中贯穿始终的新的数据和循环标准。

生成器和异步代码

请看一段代码:
}).on('close', function () {
  done(undefined, undefined);
}).on('error', function (error) {
  done(error);
});

异步APIs提供的是错误处理而非异常处理,不同APIs有不同的处理方法。而大多数错误定义是默认的,所以进行异步编程时需要花一定时间去了解。生成器则提供了新的处理方式。

Q.async()是一个实验性的类似于同步代码的异步代码生成方法,请看代码:
// 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关键字来执行异步函数。因此生成器为新的异步编程模型带来了新的思路,更符合人的思维习惯。

写在最后

限于篇幅,生成器还有两个方法留待后续讲解,.throw()和.return()。多看官方文档多动手练习,你会发现ES6更多精彩。(译者:伍昆 责编:陈秋歌)

原文链接:ES6 In Depth: Generators

相关阅读:

本译文遵循Creative Commons Attribution Share-Alike License v3.0
3
1
评论 共 1 条 请登录后发表评论
1 楼 dsjt 2015-06-24 14:26
全新的概念。

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • design-patterns-sample:我们在VandyHacks 2015上的演示的代码示例。此存储库演示了如何在iOS,Android和Rails中应用和使用少量设计模式

    确保一个类只有一个实例,并提供对其的全局访问点。 封装的“即时初始化”或“首次使用时初始化”。 问题 应用程序需要一个对象实例,并且只有一个实例。 此外,必须进行延迟初始化和全局访问。 例子 工厂 意图 ...

  • Turbo Rails 指导

    让我们继续构建项目吧 实现简单的增删改查 这一章,我们将要创建quote模型,并关联Controller通过Rails的约定 我们先通过Excalidraw来画一些草图,来描述我们要做的事儿 在Quotes#index页面中,我们将展示quotes...

  • rails 创建_使用Rails和Icecast创建在线流媒体广播

    rails 创建Hello and welcome to this article! Today I would like to talk about creating an online streaming radio with the Ruby on Rails framework. This task is not that simple but it appears that by ...

  • 如何使用React和Redux前端创建Rails项目

    by Mark Hopson 马克·霍普森(Mark Hopson) ...)创建Rails项目 (How to create a Rails project with a React and Redux front-end (plus Typescript!)) 在Rails项目中使用React和Redux设置单页Javascript...

  • Ruby on rails 实战圣经:Ruby程序语言入门

    Actually, I’mtrying to make Ruby natural, not simple. Ruby is simple in appearance, but isvery complex ... - Matz, Ruby 发明人Ruby是个美丽、灵巧而且方便又实用的程序语言,而Ruby on Rails正是 Ruby 程

  • 在 Rails 中使用 Webpack

    http://clarkdave.net/2015/01/how-to-use-webpack-with-rails/ ... webpack是一个很强大的打包工具,主要用于前端开发,可以和...它比 Rails 原本的前端管理模式要好很多,不过还是可以和 Sprockets 以及 asset...

  • 如何在 Rails 中使用 Webpack

    现在我们只需要一个入口文件,但是该属性也可以接受数组或者对象作为命名的入口点,我们之后会讲到。这里的重点是,该文件是你前端 JS 的 核心 。任何没有被这个文件引用的(或者某些依賴項引用的)将不会被编译到包...

  • Rails教程

    再拾Rails的时候发现已经到3.0.3了, 巨大的改变发现找篇中文教程都找不到,于是索性根据官方教程一边学一边翻译,就当学习笔记。水平有限,不足此处敬请谅解。   1. 环境搭建   Ruby1.8.7+ (那么由于...

  • Ruby on Rails学习笔记之Ruby基础 —— Part1 基本概念

    文章目录Part1 基本概念注释写法交互式编程启动退出脚本式编程运行options内置函数`puts``gets`变量局部变量全局变量常量伪变量变量访问方法语法参数无参数有参数默认参数可变参数函数调用无参数调用有参数调用1有...

  • springMVC的Controller使用全局变量

    使用SpringMVC的时候,如果想要在Controller中定义一个全局变量

  • [ruby on rails]Rspec的基本使用和性能优化

    一、安装 ruby使用 gem install rspec # 文件夹下执行 rspec --init # 生成 create .rspec create spec/spec_helper.rb rails添加gem group :development, :test do gem 'rspec-rails' end # 生成 rails generate ...

  • rails文件上传_使用Rails和ActionCable上传文件

    rails文件上传This is the second part of the tutorial that explains how to enable real-time communication and file uploading with Rails and ActionCable. In the previous part we have created a Rails 5.1...

  • Rails4.1 Action Controller 概述

    在此篇中你将学习到控制器的工作原理以及如何请求的周期内进行逻辑...l 如何在控制器中使用请求流l 如何限制传入控制器的参数l 为什么以及如何将数据存储在session和cookie中l 如何在请求处理过程中,使用过滤器

  • Rails

    导读: 关键字: Rails Smart Pluralization 对英文网站,我们常常需要显示一个名词的复数形式。 而Rails就提供了一个称为Inflector的工具来计算该逻辑,并且ActionView有一个wrapper方法来处理常见的复数形式,如...

  • 配置 Rails 应用程序

    这个指南涵盖了 Rails 应用程序的配置和初始化设置.通过浏览这个指南,你将能: 调整 Rails 应用程序的运作附加程序启动时运行的代码 endprologue. 初始化代码的位置 Rails 提供四个标准的位置初始化代码 ...

  • Rails3 入门之六 建立一个资源

    在blog 应用程序中。你可以通过脚手架(scaffolded)开始建立一个资源。 这将是单一的blog 提交。请输入以下命令 $ rails generate scaffold Post name:string title:string content:text脚手架将会建立一些文件和...

  • rails 笔记

    启动项目: ruby script/server -e ...在编写ruby代码时,如果要引用另一个文件中的类和模块,需要使用require关键字,但是当我们在rails中引用另一个文件中的类和模块时,rails会自动把类名称根据命名约定...

  • Ruby on Rails学习笔记(6)--Ruby中的变量

    类变量以@@为前缀,写在类声明...类变量的作用域是整个类,类中包含不同的对象,不同的对象在运行时,类变量不会被初始化。 Ruby 全局变量 全局变量以 $ 开头。未初始化的全局变量的值为 nil,在使用 -w 选项后

  • Rails4.1 Action View概述

    事实上,你可以创建一个多种类型对象的集合并以这种形式进行渲染,Rails将自动选择合适的局部视图来渲染。 3.2.5 间隔区模板 你也可以使用:spacer_template参数来指定第二个局部模板,该模板将用于主...

  • Tobit与Probit模型Stata实现代码-最新发布.zip

    Tobit与Probit模型Stata实现代码-最新发布.zip

Global site tag (gtag.js) - Google Analytics