ES6的循环与可迭代对象示例详解


Posted in Javascript onJanuary 31, 2021

本文将研究 ES6 的 for ... of 循环。

旧方法

在过去,有两种方法可以遍历 javascript。

首先是经典的 for i 循环,它使你可以遍历数组或可索引的且有 length 属性的任何对象。

for(i=0;i<things.length;i++) {
 var thing = things[i]
 /* ... */
}

其次是 for ... in 循环,用于循环一个对象的键/值对。

for(key in things) {
 if(!thing.hasOwnProperty(key)) { continue; }
 var thing = things[key]
 /* ... */
}

for ... in 循环通常被视作旁白,因为它循环了对象的每一个可枚举属性[1]。这包括原型链中父对象的属性,以及被分配为方法的所以属性。换句话说,它遍历了一些人们可能想不到的东西。使用 for ... in 通常意味着循环块中有很多保护子句,以避免出现不需要的属性。

早期的 javascript 通过库解决了这个问题。许多 JavaScript 库(例如:Prototype.js,jQuery,lodash 等)都有类似 each 或 foreach 这样的工具方法或函数,可让你无需 for i 或 for ... in 循环去遍历对象和数组。

for ... of 循环是 ES6 试图不用第三方库去解决其中一些问题的方式。

for … of

for ... of 循环

for(const thing of things) {
 /* ... */
}

它将遍历一个可迭代(iterable)对象。

可迭代对象是定义了 @@ iterator 方法的对象,而且 @@iterator 方法返回一个实现了迭代器协议的对象,或者该方法是生成器函数。

在这句话中你需要理解很多东西:

  • 可迭代的对象
  • @@iterator方法( @@是什么意思?)
  • 迭代器协议(这里的协议是什么意思?)
  • 等等,迭代(iterable)和迭代器(iterator)不是一回事?
  • 另外,生成器函数又是什么鬼?

下面逐个解决这些疑问。

内置 Iterable

首先,javascript 对象中的一些内置对象天然的可以迭代,比如最容易想到的就是数组对象。可以像下面的代码中一样在 for ... of 循环中使用数组:

const foo = [
'apples','oranges','pears'
]

for(const thing of foo) {
 console.log(thing)
}

输出结果是数组中的所有元素。

apples
oranges
pears

还有数组的 entries 方法,它返回一个可迭代对象。这个可迭代对象在每次循环中返回键和值。例如下面的代码:

const foo = [
'apples','oranges','pears'
]

for(const thing of foo.entries()) {
 console.log(thing)
}

将输出以下内容

[ 0, 'apples' ]
[ 1, 'oranges' ]
[ 2, 'pears' ]

当用下面的语法时,entries 方法会更有用

const foo = [
 'apples','oranges','pears'
]

for(const [key, value] of foo.entries()) {
 console.log(key,':',value)
}

在 for 循环中声明了两个变量:一个用于返回数组的第一项(值的键或索引),另一个用于第二项(该索引实际对应的值)。

一个普通的 javascript 对象是不可迭代的。如果你执行下面这段代码:

// 无法正常执行
const foo = {
 'apples':'oranges',
 'pears':'prunes'
}

for(const [key, value] of foo) {
 console.log(key,':',value)
}

会得到一个错误

$ node test.js
/path/to/test.js:6
for(const [key, value] of foo) {
TypeError: foo is not iterable

然而全局 Object 对象的静态 entries 方法接受一个普通对象作为参数,并返回一个可迭代对象。就像这样的程序:

const foo = {
 'apples':'oranges',
 'pears':'prunes'
}

for(const [key, value] of Object.entries(foo)) {
 console.log(key,':',value)
}

能够得到你期望的输出:

$ node test.js
apples : oranges
pears : prunes

创建自己的 Iterable

如果你想创建自己的可迭代对象,则需要花费更多的时间。你会记得前面说过:

可迭代对象是定义了 @@ iterator 方法的对象,而且 @@iterator 方法返回一个实现了迭代器协议的对象,或者该方法是生成器函数。

搞懂这些内容的最简单方法就是一步一步的去创建可迭代对象。首先,我们需要一个实现 @@iterator 方法的对象。 @@ 表示法有点误导性,我们真正  要做的是用预定义的 Symbol.iterator 符号定义方法。

如果用迭代器方法定义对象并尝试遍历:

const foo = {
 [Symbol.iterator]: function() {
 }
}

for(const [key, value] of foo) {
 console.log(key, value)
}

得到一个新错误:

for(const [key, value] of foo) {
                          ^
TypeError: Result of the Symbol.iterator method is not an object

这是 javascript 告诉我们它在试图调用 Symbol.iterator 方法,但是调用的结果不是对象。

为了消除这个错误,需要用迭代器方法来返回实现了迭代器协议的对象。这意味着迭代器方法需要返回一个有 next 键的对象,而 next 键是一个函数。

const foo = {
 [Symbol.iterator]: function() {
 return {
 next: function() {
 }
 }
 }
}

for(const [key, value] of foo) {
 console.log(key, value)
}

如果运行上面的代码,则会出现新错误。

for(const [key, value] of foo) {
                     ^
TypeError: Iterator result undefined is not an object

这次 javascript 告诉我们它试图调用 Symbol.iterator 方法,而该对象的确是一个对象,并且实现了 next 方法,但是 next 的返回值不是 javascript 预期的对象。

next 函数需要返回有特定格式的对象——有 value 和 done 这两个键。

next: function() {
 //...
 return {
 done: false,
 value: 'next value'
 }
}

done 键是可选的。如果值为 true(表示迭代器已完成迭代),则说明迭代已结束。

如果 done 为 false 或不存在,则需要 value 键。 value 键是通过循环此应该返回的值。

所以在代码中放入另一个程序,它带有一个简单的迭代器,该迭代器返回前十个偶数。

class First20Evens {
 constructor() {
 this.currentValue = 0
 }

 [Symbol.iterator]( "Symbol.iterator") {
 return {
 next: (function() {
 this.currentValue+=2
 if(this.currentValue > 20) {
  return {done:true}
 }
 return {
  value:this.currentValue
 }
 }).bind(this)
 }
 }
}

const foo = new First20Evens;
for(const value of foo) {
 console.log(value)
}

生成器

手动去构建实现迭代器协议的对象不是唯一的选择。生成器对象(由生成器函数返回)也实现了迭代器协议。上面的例子用生成器构建的话看起来像这样:

class First20Evens {
 constructor() {
 this.currentValue = 0
 }

 [Symbol.iterator]( "Symbol.iterator") {
 return function*() {
 for(let i=1;i<=10;i++) {
 if(i % 2 === 0) {
  yield i
 }
 }
 }()
 }
}

const foo = new First20Evens;
for(const item of foo) {
 console.log(item)
}

本文不会过多地介绍生成器,如果你需要入门的话可以看这篇文章。今天的重要收获是,我们可以使自己的 Symbol.iterator 方法返回一个生成器对象,并且该生成器对象能够在 for ... of 循环中“正常工作”。 “正常工作”是指循环能够持续的在生成器上调用 next,直到生成器停止 yield 值为止。

$ node sample-program.js
2
4
6
8
10

参考资料

对象的每个可枚举属性: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

总结

到此这篇关于ES6的循环与可迭代对象的文章就介绍到这了,更多相关ES6循环与可迭代对象内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
彪哥1.1(智能表格)提供下载
Sep 07 Javascript
学习YUI.Ext第七日-View&amp;JSONView Part Two-一个画室网站的案例
Mar 10 Javascript
javascript Array.prototype.slice使用说明
Oct 11 Javascript
select标签模拟/美化方法采用JS外挂式插件
Apr 01 Javascript
chrome浏览器不支持onmouseleave事件的解决技巧
May 31 Javascript
js之onload事件的一点使用心得
Aug 14 Javascript
Jquery创建一个层当鼠标移动到层上面不消失效果
Dec 12 Javascript
高性能JavaScript DOM编程(1)
Aug 11 Javascript
ES6新特性之字符串的扩展实例分析
Apr 01 Javascript
浅谈Angular 中何时取消订阅
Nov 22 Javascript
Webpack之tree-starking 解析
Sep 11 Javascript
Vue开发之封装分页组件与使用示例
Apr 25 Javascript
Javascript生成器(Generator)的介绍与使用
Jan 31 #Javascript
Node.js中的异步生成器与异步迭代详解
Jan 31 #Javascript
Vite和Vue CLI的优劣
Jan 30 #Vue.js
如何使用RoughViz可视化Vue.js中的草绘图表
Jan 30 #Vue.js
Element-ui 自带的两种远程搜索(模糊查询)用法讲解
Jan 29 #Javascript
小程序实现列表倒计时功能
Jan 29 #Javascript
JS相册图片抖动放大展示效果的示例代码
Jan 29 #Javascript
You might like
php zend 相对路径问题
2009/01/12 PHP
深入解析php之sphinx
2013/05/15 PHP
laravel通过a标签从视图向控制器实现传值
2019/10/15 PHP
php实现JWT(json web token)鉴权实例详解
2019/11/05 PHP
javascript之可拖动的iframe效果代码
2008/08/01 Javascript
jquery选择器的选择使用及性能介绍
2013/01/16 Javascript
getComputedStyle与currentStyle获取样式(style/class)
2013/03/19 Javascript
javascript获得网页窗口实际大小的示例代码
2013/09/21 Javascript
jquery Ajax 实现加载数据前动画效果的示例代码
2014/02/07 Javascript
基于javascript实现动态时钟效果
2020/08/18 Javascript
微信小程序 MD5加密登录密码详解及实例代码
2017/01/12 Javascript
vue如何获取点击事件源的方法
2017/08/10 Javascript
详解Vue.js v-for不支持IE9的解决方法
2018/12/29 Javascript
javascript数组常见操作方法实例总结【连接、添加、删除、去重、排序等】
2019/06/13 Javascript
vue 使用高德地图vue-amap组件过程解析
2019/09/07 Javascript
JS立即执行的匿名函数用法分析
2019/11/04 Javascript
javascript canvas实现简易时钟例子
2020/09/05 Javascript
[01:59]DOTA2首部纪录片《Free to play》预告片
2014/03/12 DOTA
[01:11:08]Winstrike vs NB 2018国际邀请赛淘汰赛BO1 8.21
2018/08/22 DOTA
python线程锁(thread)学习示例
2013/12/04 Python
python利用dir函数查看类中所有成员函数示例代码
2017/09/08 Python
对sklearn的使用之数据集的拆分与训练详解(python3.6)
2018/12/14 Python
Python进程间通信 multiProcessing Queue队列实现详解
2019/09/23 Python
opencv 图像滤波(均值,方框,高斯,中值)
2020/07/08 Python
Python Celery异步任务队列使用方法解析
2020/08/10 Python
Python如何把字典写入到CSV文件的方法示例
2020/08/23 Python
python学习之使用Matplotlib画实时的动态折线图的示例代码
2021/02/25 Python
委托书样本
2014/04/02 职场文书
赵乐秦在党的群众路线教育实践活动总结大会上的讲话稿
2014/10/25 职场文书
教师考核表个人总结
2015/02/12 职场文书
宇宙与人观后感
2015/06/05 职场文书
干货:如何写好工作计划!
2019/05/17 职场文书
中学生打架《检讨书》范文
2019/08/12 职场文书
Java 语言中Object 类和System 类详解
2021/07/07 Java/Android
Nginx的基本概念和原理
2022/03/21 Servers
Python图像处理库PIL详细使用说明
2022/04/06 Python