详解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 相关文章推荐
js 设置选中行的样式的实现代码
May 24 Javascript
使用js 设置url参数
Jul 08 Javascript
JQuery中使文本框获得焦点的方法实例分析
Feb 28 Javascript
jQuery实现在下拉列表选择时获取json数据的方法
Apr 16 Javascript
javascript弹出带文字信息的提示框效果
Jul 19 Javascript
Angularjs中使用layDate日期控件示例
Jan 11 Javascript
JavaScript之DOM_动力节点Java学院整理
Jul 03 Javascript
vue.js内部自定义指令与全局自定义指令的实现详解(利用directive)
Jul 11 Javascript
浅谈mint-ui 填坑之路
Nov 06 Javascript
还不懂递归?读完这篇文章保证你会懂
Jul 29 Javascript
JavaScript函数式编程(Functional Programming)组合函数(Composition)用法分析
May 22 Javascript
antd配置config-overrides.js文件的操作
Oct 31 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
NOD32 v2.70.32 简体中文封装版 提供下载了
2007/02/27 PHP
php中的curl_multi系列函数使用例子
2014/07/29 PHP
Yii2针对指定url的生成及图片等的引入方法小结
2016/07/18 PHP
php使用Swoole实现毫秒级定时任务的方法
2020/09/04 PHP
javascript定时保存表单数据的代码
2011/03/17 Javascript
jquery 操作DOM案例代码分享
2012/04/05 Javascript
jquery实现瀑布流效果分享
2014/03/26 Javascript
javascript实例分享---具有立体效果的图片特效
2014/06/08 Javascript
jquery在ie7下选择器的问题导致append失效的解决方法
2016/01/10 Javascript
Bootstrap页面布局基础知识全面解析
2016/06/13 Javascript
JS提示:Uncaught SyntaxError:Unexpected token ) 错误的解决方法
2016/08/19 Javascript
JavaScript省市区三级联动菜单效果
2016/09/21 Javascript
jQuery实现的简单拖动层示例
2017/02/22 Javascript
vue-cli 默认路由再子路由选中下的选中状态问题及解决代码
2018/09/06 Javascript
axios取消请求的实践记录分享
2018/09/26 Javascript
微信小程序性能优化之checkSession的使用
2019/03/06 Javascript
JS中实现一个下载进度条及播放进度条的代码
2019/06/10 Javascript
Vue3配置axios跨域实现过程解析
2020/11/25 Vue.js
python随机取list中的元素方法
2018/04/08 Python
Python基于whois模块简单识别网站域名及所有者的方法
2018/04/23 Python
Python爬虫之pandas基本安装与使用方法示例
2018/08/08 Python
pytorch::Dataloader中的迭代器和生成器应用详解
2020/01/03 Python
Python编程快速上手——选择性拷贝操作案例分析
2020/02/28 Python
Python 如何调试程序崩溃错误
2020/08/03 Python
Python使用pycharm导入pymysql教程
2020/09/16 Python
python 用Matplotlib作图中有多个Y轴
2020/11/28 Python
Myprotein瑞典官方网站:畅销欧洲英国运动营养品牌
2018/01/22 全球购物
如何打印出当前源文件的文件名以及源文件的当前行号
2015/04/05 面试题
Why we need EJB
2016/10/20 面试题
大学活动策划书范文
2014/01/10 职场文书
写给女朋友的道歉信
2014/01/12 职场文书
民政局副局长民主生活会个人对照检查材料
2014/09/19 职场文书
土木工程专业本科生求职信
2014/10/01 职场文书
2015年政教主任工作总结
2015/07/23 职场文书
JavaScript如何优化逻辑判断代码详解
2021/06/08 Javascript
PC版《死亡搁浅导剪版》现已发售 展开全新的探险
2022/04/03 其他游戏