深入理解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 相关文章推荐
jQuery 技巧小结
Apr 02 Javascript
Javascript 多浏览器兼容总结(实战经验)
Oct 30 Javascript
JavaScript中this的使用详解
Nov 08 Javascript
在页面中js获取光标/鼠标的坐标及光标的像素坐标
Nov 11 Javascript
jQuery 事件的命名空间简单了解
Nov 22 Javascript
DOM操作一些常用的属性汇总
Mar 13 Javascript
JavaScript实现当网页加载完成后执行指定函数的方法
Mar 21 Javascript
jQuery往textarea中光标所在位置插入文本的方法
Jun 26 Javascript
跟我学习javascript的定时器
Nov 19 Javascript
VueJS全面解析
Nov 10 Javascript
微信公众号开发之微信支付代码记录的实现
Oct 16 Javascript
微信小程序分享小程序码的生成(带参数)以及参数的获取
Mar 25 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 file_get_contents函数轻松采集html数据
2010/04/22 PHP
PHP中批量生成静态html(命令行下运行PHP)
2014/04/19 PHP
ThinkPHP调用百度翻译类实现在线翻译
2014/06/26 PHP
微信支付PHP SDK之微信公众号支付代码详解
2015/12/09 PHP
微信支付开发动态链接Native支付
2016/07/12 PHP
PHP 中使用ajax时一些常见错误总结整理
2017/02/27 PHP
php输出反斜杠的实例方法
2019/09/19 PHP
PHP copy函数使用案例代码解析
2020/09/01 PHP
jquery获取tr中控件值并操作tr实现思路
2013/03/27 Javascript
当jQuery1.7遇上focus方法的问题
2014/01/26 Javascript
JS组件Bootstrap按钮组与下拉按钮详解
2016/05/10 Javascript
推荐VSCode 上特别好用的 Vue 插件之vetur
2017/09/14 Javascript
JavaScript原生实现观察者模式的示例
2017/12/15 Javascript
vue+webpack实现异步加载三种用法示例详解
2018/04/24 Javascript
JS加密插件CryptoJS实现的DES加密示例
2018/08/16 Javascript
vue父组件给子组件的组件传值provide inject的方法
2019/10/23 Javascript
Vue中nprogress页面加载进度条的方法实现
2020/11/13 Javascript
element-ui中el-upload多文件一次性上传的实现
2020/12/02 Javascript
Python中datetime模块参考手册
2017/01/13 Python
浅谈python装饰器探究与参数的领取
2017/12/01 Python
python3+mysql查询数据并通过邮件群发excel附件
2018/02/24 Python
详解django中使用定时任务的方法
2018/09/27 Python
python原类、类的创建过程与方法详解
2019/07/19 Python
Python封装成可带参数的EXE安装包实例
2019/08/24 Python
python用TensorFlow做图像识别的实现
2020/04/21 Python
在 Python 中使用 7zip 备份文件的操作
2020/12/11 Python
英国第一摩托车和摩托车越野配件商店:GhostBikes
2019/03/10 全球购物
Nip + Fab官网:英国美容品牌
2019/08/26 全球购物
讲座主持词
2014/03/20 职场文书
村长贪污检举信
2014/04/04 职场文书
数控技校生自我鉴定
2014/04/19 职场文书
2014年信息技术工作总结
2014/12/16 职场文书
悬空寺导游词
2015/02/05 职场文书
后勤工作个人总结
2015/02/28 职场文书
中学生自我评价2015
2015/03/03 职场文书
高三毕业感言
2015/07/30 职场文书