详解ES6语法之可迭代协议和迭代器协议


Posted in Javascript onJanuary 13, 2018

ECMAScript 2015的几个补充,并不是新的内置或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现。
有两个协议:可迭代协议和迭代器协议。

可迭代协议

可迭代协议允许 JavaScript 对象去定义或定制它们的迭代行为, 例如(定义)在一个 for..of 结构中什么值可以被循环(得到)。一些内置类型都是内置的可迭代对象并且有默认的迭代行为, 比如 Array or Map, 另一些类型则不是 (比如Object) 。

Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for...of循环(详见下文)。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口,调用Symbol.iterator方法,返回该对象的默认遍历器。

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可迭代的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。

为了变成可迭代对象, 一个对象必须实现(或者它原型链的某个对象)必须有一个名字是 Symbol.iterator 的属性:

迭代器协议

该迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值。

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

迭代器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

Iterator 的遍历过程是这样的。

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

var someString = "hi";
typeof someString[Symbol.iterator]; // "function"
var iterator = someString[Symbol.iterator]();
iterator + "";  // "[object String Iterator]"
iterator.next()    // { value: "h", done: false }
iterator.next();   // { value: "i", done: false }
iterator.next();   // { value: undefined, done: true }

原生具备 Iterator 接口的数据结构如下。

  1. Array
  2. Map
  3. Set
  4. String
  5. TypedArray
  6. 函数的 arguments 对象
  7. NodeList 对象

注意对象是不具备 Iterator 接口的,一个对象如果要具备可被for...of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)。

调用 Iterator 接口的场合

有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法),除了下文会介绍的for...of循环,解构赋值, 扩展运算符其实也会调用默认的Iterator 接口。

实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。

  1. for...of
  2. Array.from()
  3. Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
  4. Promise.all()
  5. Promise.race()

for...of

for...of 循环是最新添加到 JavaScript 循环系列中的循环。

它结合了其兄弟循环形式 for 循环和 for...in 循环的优势,可以循环任何可迭代(也就是遵守可迭代协议)类型的数据。默认情况下,包含以下数据类型:String、Array、Map 和 Set,注意不包含 Object 数据类型(即 {})。默认情况下,对象不可迭代。

在研究 for...of 循环之前,先快速了解下其他 for 循环,看看它们有哪些不足之处。

for 循环

for 循环的最大缺点是需要跟踪计数器和退出条件。我们使用变量 i 作为计数器来跟踪循环并访问数组中的值。我们还使用 Array.length 来判断循环的退出条件。

虽然 for 循环在循环数组时的确具有优势,但是某些数据结构不是数组,因此并非始终适合使用 loop 循环。

for...in 循环

for...in 循环改善了 for 循环的不足之处,它消除了计数器逻辑和退出条件。但是依然需要使用 index 来访问数组的值.

此外,当你需要向数组中添加额外的方法(或另一个对象)时,for...in 循环会带来很大的麻烦。因为 for...in 循环循环访问所有可枚举的属性,意味着如果向数组的原型中添加任何其他属性,这些属性也会出现在循环中。这就是为何在循环访问数组时,不建议使用 for...in 循环。

注意: forEach 循环 是另一种形式的 JavaScript 循环。但是,forEach() 实际上是数组方法,因此只能用在数组中。也无法停止或退出 forEach 循环。如果希望你的循环中出现这种行为,则需要使用基本的 for 循环。

for...of 循环

for...of 循环用于循环访问任何可迭代的数据类型。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
 console.log(digit);
}

可以随时停止或退出 for...of 循环。

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

for (const digit of digits) {
 if (digit % 2 === 0) {
  continue;
 }
 console.log(digit); //1,3,5,7,9
}

不用担心向对象中添加新的属性。for...of 循环将只循环访问对象中的值。

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

Javascript 相关文章推荐
JavaScript 学习笔记(十四) 正则表达式
Jan 22 Javascript
javascript转换字符串为dom对象(字符串动态创建dom)
May 10 Javascript
ExtJS4 Grid改变单元格背景颜色及Column render学习
Feb 06 Javascript
JS自定义功能函数实现动态添加网址参数修改网址参数值
Aug 02 Javascript
利用CSS、JavaScript及Ajax实现高效的图片预加载
Oct 16 Javascript
简介JavaScript中的setHours()方法的使用
Jun 11 Javascript
详解JavaScript中的客户端消息框架设计原理
Jun 24 Javascript
DOM事件阶段以及事件捕获与事件冒泡先后执行顺序(图文详解)
Aug 18 Javascript
JavaScript编写检测用户所使用的浏览器的代码示例
May 05 Javascript
jquery自定义插件——window的实现【示例代码】
May 06 Javascript
Windows系统下安装Node.js的步骤图文详解
Nov 15 Javascript
如何HttpServletRequest文件对象并储存
Aug 14 Javascript
详解如何在React组件“外”使用父组件的Props
Jan 12 #Javascript
vue使用element-ui的el-input监听不了回车事件的解决方法
Jan 12 #Javascript
微信小程序实现的涂鸦功能示例【附源码下载】
Jan 12 #Javascript
js 索引下标之li集合绑定点击事件
Jan 12 #Javascript
简单的Vue SSR的示例代码
Jan 12 #Javascript
详解如何在react中搭建d3力导向图
Jan 12 #Javascript
关于axios不能使用Vue.use()浅析
Jan 12 #Javascript
You might like
thinkPHP的Html模板标签使用方法
2012/11/13 PHP
PHP文件上传主要代码讲解
2013/09/30 PHP
php四种基础算法代码实例
2013/10/29 PHP
destoon实现会员商铺中指定会员或会员组投放广告的方法
2014/08/21 PHP
了解jQuery技巧来提高你的代码(个人觉得那个jquery的手册很不错)
2012/02/10 Javascript
Node.js 异步编程之 Callback介绍(一)
2015/03/30 Javascript
JavaScript中的数组遍历forEach()与map()方法以及兼容写法介绍
2016/05/19 Javascript
浅谈layer的iframe弹窗给里面的标签赋值的问题
2016/11/10 Javascript
js实现键盘自动打字效果
2016/12/23 Javascript
AngularJS使用ng-inlude指令加载页面失败的原因与解决方法
2017/01/19 Javascript
详解如何在Angular优雅编写HTTP请求
2018/12/05 Javascript
JavaScript模板引擎应用场景及实现原理详解
2018/12/14 Javascript
vue-router源码之history类的浅析
2019/05/21 Javascript
解决layui数据表格排序图标被超出的表头挤出去的问题
2019/09/19 Javascript
vuex 多模块时 模块内部的mutation和action的调用方式
2020/07/24 Javascript
Python isinstance判断对象类型
2008/09/06 Python
Python实现求两个csv文件交集的方法
2017/09/06 Python
Numpy中stack(),hstack(),vstack()函数用法介绍及实例
2018/01/09 Python
Python Unittest自动化单元测试框架详解
2018/04/04 Python
Django如何配置mysql数据库
2018/05/04 Python
python 下 CMake 安装配置 OPENCV 4.1.1的方法
2019/09/30 Python
python爬虫之遍历单个域名
2019/11/20 Python
基于python的列表list和集合set操作
2019/11/24 Python
torchxrayvision包安装过程(附pytorch1.6cpu版安装)
2020/08/26 Python
使用CSS3编写灰阶滤镜来制作黑白照片效果的方法
2016/05/09 HTML / CSS
中国网上药店领导者:1药网
2017/02/16 全球购物
阿联酋网上花店:Ferns N Petals
2018/02/14 全球购物
汉森冲浪板:Hansen Surfboards
2018/05/19 全球购物
美国迪克体育用品商店:DICK’S Sporting Goods
2018/07/24 全球购物
一月红领巾广播稿
2014/02/11 职场文书
安全生产目标管理责任书
2014/07/25 职场文书
2014年重阳节敬老活动方案
2014/09/16 职场文书
2015年度党风廉政建设工作情况汇报
2015/01/02 职场文书
遗嘱格式范本
2015/08/07 职场文书
高三数学复习备考教学反思
2016/02/18 职场文书
服务器间如何实现文件共享
2022/05/20 Servers