深入理解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 相关文章推荐
定时器(setTimeout/setInterval)调用带参函数失效解决方法
Mar 26 Javascript
JavaScript禁止用户多次提交的两种方法
Jul 24 Javascript
jQuery实现大图轮播
Feb 13 Javascript
canvas时钟效果
Feb 16 Javascript
jQuery手风琴的简单制作
May 12 jQuery
Vue项目中跨域问题解决方案
Jun 05 Javascript
React 组件间的通信示例
Jun 14 Javascript
JavaScript 下载svg图片为png格式
Jun 21 Javascript
js实现敏感词过滤算法及实现逻辑
Jul 24 Javascript
使用vue实现各类弹出框组件
Jul 03 Javascript
JS通过识别id、value值对checkbox设置选中状态
Feb 19 Javascript
jquery插件实现搜索历史
Apr 24 jQuery
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
一键删除顽固的空文件夹 软件下载
2007/01/26 PHP
php中var_export与var_dump的区别分析
2010/08/21 PHP
PHP中创建空文件的代码[file_put_contents vs touch]
2012/01/20 PHP
探讨如何使用SimpleXML函数来加载和解析XML文档
2013/06/07 PHP
destoon实现调用图文新闻的方法
2014/08/21 PHP
利用php抓取蜘蛛爬虫痕迹的示例代码
2016/09/30 PHP
PHP CURL实现模拟登陆并上传文件操作示例
2020/01/02 PHP
数据结构之利用PHP实现二分搜索树
2020/10/25 PHP
js 覆盖和重载 函数
2009/09/25 Javascript
基于Jquery制作的幻灯片图集效果打包下载
2011/02/12 Javascript
js判断两个日期是否相等的方法
2013/09/10 Javascript
javaScript 页面自动加载事件详解
2014/02/10 Javascript
Javascript 计算字符串在localStorage中所占字节数
2015/10/21 Javascript
每天一篇javascript学习小结(Array数组)
2015/11/11 Javascript
jquery实现右侧栏菜单选择操作
2016/03/04 Javascript
js实现一个可以兼容PC端和移动端的div拖动效果实例
2016/12/09 Javascript
详解vue项目优化之按需加载组件-使用webpack require.ensure
2017/06/13 Javascript
原生js实现的移动端可拖动进度条插件功能详解
2019/08/15 Javascript
VUE中setTimeout和setInterval自动销毁案例
2020/09/07 Javascript
vue element实现表格合并行数据
2020/11/30 Vue.js
[01:05]DOTA2完美大师赛趣味视频之选手教你打职业
2017/11/23 DOTA
python 查找文件夹下所有文件 实现代码
2009/07/01 Python
Python数据分析中Groupby用法之通过字典或Series进行分组的实例
2017/12/08 Python
Python yield与实现方法代码分析
2018/02/06 Python
利用Python批量提取Win10锁屏壁纸实战教程
2018/03/27 Python
Python格式化日期时间操作示例
2018/06/28 Python
python移位运算的实现
2019/07/15 Python
python中的错误如何查看
2020/07/08 Python
韩国著名的在线综合购物网站:Akmall
2016/08/07 全球购物
世界汽车零件:World Car Parts
2019/09/04 全球购物
物流管理专业职业生涯规划书
2014/01/06 职场文书
便利店的创业计划书
2014/01/15 职场文书
试用期自我鉴定范文
2014/03/20 职场文书
教师节促销方案
2014/03/22 职场文书
步步惊心观后感
2015/06/12 职场文书
互联网的下一个风口:新的独角兽将诞生
2019/08/02 职场文书