深入理解ES6的迭代器与生成器


Posted in Javascript onAugust 19, 2017

本文介绍了深入理解ES6的迭代器与生成器,分享给大家,具体如下:

循环语句的问题

var colors = ["red", "green", "blue"];
for(var i=0; i<colors.length; i++){
  console.log(colors[i]);
}

在ES6之前,这种标准的for循环,通过变量来跟踪数组的索引。如果多个循环嵌套就需要追踪多个变量,代码复杂度会大大增加,也容易产生错用循环变量的bug。

迭代器的出现旨在消除这种复杂性并减少循环中的错误。

什么是迭代器

我们先感受一下用ES5语法模拟创建一个迭代器:

function createIterator(items) {
  var i = 0;
  
  return { // 返回一个迭代器对象
    next: function() { // 迭代器对象一定有个next()方法
      var done = (i >= items.length);
      var value = !done ? items[i++] : undefined;
      
      return { // next()方法返回结果对象
        value: value,
        done: done
      };
    }
  };
}

var iterator = createIterator([1, 2, 3]);

console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefiend, done: true}"
// 之后所有的调用都会返回相同内容
console.log(iterator.next()); // "{ value: undefiend, done: true}"

以上,我们通过调用createIterator()函数,返回一个对象,这个对象存在一个next()方法,当next()方法被调用时,返回格式{ value: 1, done: false}的结果对象。

因此,我们可以这么定义:迭代器是一个拥有next()方法的特殊对象,每次调用next()都返回一个结果对象。

借助这个迭代器对象,我们来改造刚开始那个标准的for循环【暂时先忘记ES6的for-of循环新特性】:

var colors = ["red", "green", "blue"];
var iterator = createIterator(colors);
while(!iterator.next().done){
  console.log(iterator.next().value);
}

what?,消除循环变量而已,需要搞这么麻烦,代码上不是得不偿失了吗?

并非如此,毕竟createIterator()只需写一次,就可以一直复用。不过ES6引入了生成器对象,可以让创建迭代器的过程变得更加简单。

什么是生成器

生成器是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字yield。

function *createIterator(items) {
  for(let i=0; i<items.length; i++) {
    yield items[i];
  }
}

let iterator = createIterator([1, 2, 3]);

// 既然生成器返回的是迭代器,自然就可以调用迭代器的next()方法
console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefiend, done: true}"
// 之后所有的调用都会返回相同内容
console.log(iterator.next()); // "{ value: undefiend, done: true}"

上面,我们用ES6的生成,大大简化了迭代器的创建过程。我们给生成器函数createIterator()传入一个items数组,函数内部,for循环不断从数组中生成新的元素放入迭代器中,每遇到一个yield语句循环都会停止;每次调用迭代器的next()方法,循环便继续运行并停止在下一条yield语句处。

生成器的创建方式

生成器是个函数:

function *createIterator(items) { ... }

可以用函数表达式方式书写:

let createIterator = function *(item) { ... }

也可以添加到对象中,ES5风格对象字面量:

let o = {
  createIterator: function *(items) { ... }
};

let iterator = o.createIterator([1, 2, 3]);

ES6风格的对象方法简写方式:

let o = {
  *createIterator(items) { ... }
};

let iterator = o.createIterator([1, 2, 3]);

可迭代对象

在ES6中,所有的集合对象(数组、Set集合及Map集合)和字符串都是可迭代对象,可迭代对象都绑定了默认的迭代器。

来了来了,姗姗来迟的ES6循环新特性for-of:

var colors = ["red", "green", "blue"];
for(let color of colors){
  console.log(color);
}

for-of循环,可作用在可迭代对象上,正是利用了可迭代对象上的默认迭代器。大致过程是:for-of循环每执行一次都会调用可迭代对象的next()方法,并将迭代器返回的结果对象的value属性存储在变量中,循环将继续执行这一过程直到返回对象的done属性的值为true。

如果只需要迭代数组或集合中的值,用for-of循环代替for循环是个不错的选择。

访问默认迭代器

可迭代对象,都有一个Symbol.iterator方法,for-of循环时,通过调用colors数组的Symbol.iterator方法来获取默认迭代器的,这一过程是在JavaScript引擎背后完成的。

我们可以主动获取一下这个默认迭代器来感受一下:

let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();

console.log(iterator.next()); // "{ value: 1, done: false}"
console.log(iterator.next()); // "{ value: 2, done: false}"
console.log(iterator.next()); // "{ value: 3, done: false}"
console.log(iterator.next()); // "{ value: undefined, done: true}"

在这段代码中,通过Symbol.iterator获取了数组values的默认迭代器,并用它遍历数组中的元素。在JavaScript引擎中执行for-of循环语句也是类似的处理过程。

用Symbol.iterator属性来检测对象是否为可迭代对象:

function isIterator(object) {
  return typeof object[Symbol.iterator] === "function";
}

console.log(isIterable([1, 2, 3])); // true
console.log(isIterable(new Set())); // true
console.log(isIterable(new Map())); // true
console.log(isIterable("Hello")); // true

创建可迭代对象

当我们在创建对象时,给Symbol.iterator属性添加一个生成器,则可以将其变成可迭代对象:

let collection = {
  items: [],
  *[Symbol.iterator]() { // 将生成器赋值给对象的Symbol.iterator属性来创建默认的迭代器
    for(let item of this.items) {
      yield item;
    }
  }
};

collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

for(let x of collection) {
  console.log(x);
}

内建迭代器

ES6中的集合对象,数组、Set集合和Map集合,都内建了三种迭代器:

  • entries() 返回一个迭代器,其值为多个键值对。如果是数组,第一个元素是索引位置;如果是Set集合,第一个元素与第二个元素一样,都是值。
  • values() 返回一个迭代器,其值为集合的值。
  • keys() 返回一个迭代器,其值为集合中的所有键名。如果是数组,返回的是索引;如果是Set集合,返回的是值(Set的值被同时用作键和值)。

不同集合的默认迭代器

每个集合类型都有一个默认的迭代器,在for-of循环中,如果没有显式指定则使用默认的迭代器。按常规使用习惯,我们很容易猜到,数组和Set集合的默认迭代器是values(),Map集合的默认迭代器是entries()。

请看以下示例:

let colors = [ "red", "green", "blue"];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "print");

// 与调用colors.values()方法相同
for(let value of colors) {
  console.log(value);
}

// 与调用tracking.values()方法相同
for(let num of tracking) {
  console.log(num);
}

// 与调用data.entries()方法相同
for(let entry of data) {
  console.log(entry);
}

这段代码会输入以下内容:

"red"
"green"
"blue"
1234
5678
9012
["title", "Understanding ECMAScript 6"]
["format", "print"]

for-of循环配合解构特性,操纵数据会更方便:

for(let [key, value] of data) {
  console.log(key + "=" + value);
}

展开运算符操纵可迭代对象

let set = new Set([1, 2, 3, 4, 5]),
  array = [...set];
  
console.log(array); // [1,2,3,4,5]

展开运算符可以操作所有的可迭代对象,并根据默认迭代器来选取要引用的值,从迭代器读取所有值。然后按返回顺序将它们依次插入到数组中。因此如果想将可迭代对象转换为数组,用展开运算符是最简单的方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript 网页水印(非图片水印)实现代码
Mar 01 Javascript
JavaScript移除数组元素减少长度的方法
Sep 05 Javascript
jquery绑定事件不生效的解决方法
Feb 11 Javascript
DOM节点删除函数removeChild()用法实例
Jan 12 Javascript
AngularJS基础 ng-mouseover 指令简单示例
Aug 02 Javascript
vue.js指令v-for使用及索引获取
Nov 03 Javascript
npm 更改默认全局路径以及国内镜像的方法
May 16 Javascript
JS div匀速移动动画与变速移动动画代码实例
Mar 26 Javascript
node.js事件轮询机制原理知识点
Dec 22 Javascript
vue根据条件不同显示不同按钮的操作
Aug 04 Javascript
JavaScript实现随机点名小程序
Oct 29 Javascript
一文搞懂redux在react中的初步用法
Jun 09 Javascript
Mui使用jquery并且使用点击跳转新窗口的实例
Aug 19 #jQuery
Vue自定义事件(详解)
Aug 19 #Javascript
Vue内容分发slot(全面解析)
Aug 19 #Javascript
简单的网页广告特效实例
Aug 19 #Javascript
JavaScript 完成注册页面表单校验的实例
Aug 19 #Javascript
JS模拟超市简易收银台小程序代码解析
Aug 18 #Javascript
详解JS数组Reduce()方法详解及高级技巧
Aug 18 #Javascript
You might like
php array_slice函数的使用以及参数详解
2008/08/30 PHP
PHPThumb图片处理实例
2014/05/03 PHP
实例讲解PHP设计模式编程中的简单工厂模式
2016/02/29 PHP
php 数组处理函数extract详解及实例代码
2016/11/23 PHP
JQuery自适应IFrame高度(支持嵌套 兼容IE,ff,safafi,chrome)
2011/03/28 Javascript
js动态创建表格,删除行列的小例子
2013/07/20 Javascript
Javascript 浮点运算精度问题分析与解决
2014/03/26 Javascript
js获取元素的标签名实现方法
2016/10/08 Javascript
解析javascript图片懒加载与预加载的分析总结
2016/10/27 Javascript
RGB和YUV 多媒体编程基础详细介绍
2016/11/04 Javascript
jQuery的三种bind/One/Live/On事件绑定使用方法
2017/02/23 Javascript
JS如何判断浏览器类型和详细区分IE各版本浏览器
2017/03/04 Javascript
layer弹出层框架alert与msg详解
2017/03/14 Javascript
使用D3.js制作图表详解
2017/08/13 Javascript
基于 Vue 的树形选择组件的示例代码
2017/08/18 Javascript
简单介绍react redux的中间件的使用
2018/04/06 Javascript
解决axios会发送两次请求,有个OPTIONS请求的问题
2018/10/25 Javascript
搭建基于express框架运行环境的方法步骤
2018/11/15 Javascript
layui实现图片虚拟路径上传,预览和删除的例子
2019/09/25 Javascript
Python实现各种排序算法的代码示例总结
2015/12/11 Python
在Linux系统上通过uWSGI配置Nginx+Python环境的教程
2015/12/25 Python
Python中生成Epoch的方法
2017/04/26 Python
Python功能点实现:函数级/代码块级计时器
2019/01/02 Python
用Python实现大文本文件切割的方法
2019/01/12 Python
Python常用模块sys,os,time,random功能与用法实例分析
2020/01/07 Python
Html5 Canvas实现图片标记、缩放、移动和保存历史状态功能 (附转换公式)
2020/03/18 HTML / CSS
一站式跨境收款解决方案:Payoneer(派安盈)
2018/09/06 全球购物
某公司.Net方向面试题
2014/04/24 面试题
Java基础面试题
2014/07/19 面试题
数控专业个人求职信范文
2014/02/05 职场文书
股份合作协议书范本
2014/04/14 职场文书
优秀教师演讲稿
2014/05/06 职场文书
有子女的离婚协议书怎么写(范本)
2014/09/29 职场文书
拯救大兵瑞恩观后感
2015/06/09 职场文书
配置nginx负载均衡
2022/05/06 Servers
openEuler 搭建java开发环境的详细过程
2022/06/10 Servers