深入理解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 DOM 学习第七章 表单的扩展
Feb 19 Javascript
jQuery AjaxQueue改进步骤
Oct 06 Javascript
JQuery入门—JQuery程序的代码风格详细介绍
Jan 03 Javascript
Jquery封装tab自动切换效果的具体实现
Jul 13 Javascript
jquery实现网页查找功能示例分享
Feb 12 Javascript
使用jQuery简单实现模拟浏览器搜索功能
Dec 21 Javascript
纯JavaScript代码实现移动设备绘图解锁
Oct 16 Javascript
详解如何让Express支持async/await
Oct 09 Javascript
vue 组件使用中的一些细节点
Apr 25 Javascript
浅谈Vue render函数在ElementUi中的应用
Sep 06 Javascript
vue-cli中安装方法(图文详细步骤)
Dec 12 Javascript
vue输入节流,避免实时请求接口的实例代码
Oct 30 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读取MySQL数据代码
2008/06/05 PHP
PHP 数组遍历顺序理解
2009/09/09 PHP
PHP函数http_build_query使用详解
2014/08/20 PHP
php中Socket创建与监听实现方法
2015/01/05 PHP
PHP缓冲区用法总结
2016/02/14 PHP
php+websocket 实现的聊天室功能详解
2020/05/27 PHP
(JS实现)MapBar中坐标的加密和解密的脚本
2007/05/16 Javascript
JQuery 插件模板 制作jquery插件的朋友可以参考下
2010/03/17 Javascript
菜鸟javascript基础资料整理2
2010/12/06 Javascript
JS实现动画兼容性的transition和transform实例分析
2016/12/13 Javascript
JS正则表达式之非捕获分组用法实例分析
2016/12/28 Javascript
Bootstrap 中data-[*] 属性的整理
2018/03/13 Javascript
详解小程序之简单登录注册表单验证
2019/05/13 Javascript
three.js欧拉角和四元数的使用方法
2020/07/26 Javascript
原生JavaScript实现五子棋游戏
2020/11/09 Javascript
[01:03:27]Optic vs VGJ.S 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
Python中的Django基本命令实例详解
2018/07/15 Python
django+echart绘制曲线图的方法示例
2018/11/26 Python
详解Python3注释知识点
2019/02/19 Python
python实现转圈打印矩阵
2019/03/02 Python
实例讲解Python中整数的最大值输出
2019/03/17 Python
使用Python在Windows下获取USB PID&amp;VID的方法
2019/07/02 Python
python实现图片九宫格分割
2021/03/07 Python
Python Process多进程实现过程
2019/10/22 Python
html5+css3进度条倒计时动画特效代码【推荐】
2016/03/08 HTML / CSS
New Balance英国官方网站:始于1906年,百年慢跑品牌
2016/12/07 全球购物
美国知名保健品网站:LuckyVitamin(支持中文)
2017/08/09 全球购物
信息技术专业大学生个人的自我评价
2013/10/05 职场文书
餐厅总经理岗位职责
2013/12/31 职场文书
关于安全演讲稿
2014/05/09 职场文书
公务员试用期满考核材料
2014/05/22 职场文书
硕士学位论文评语
2014/12/31 职场文书
人事任命通知书
2015/04/21 职场文书
2016秋季小学开学寄语
2015/12/03 职场文书
pytorch查看网络参数显存占用量等操作
2021/05/12 Python
SQL实现LeetCode(180.连续的数字)
2021/08/04 MySQL