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 相关文章推荐
编程语言JavaScript简介
Oct 16 Javascript
深入理解JavaScript中的对象复制(Object Clone)
May 18 Javascript
Bootstrap表单布局
Jul 19 Javascript
基于Vue.js实现简单搜索框
Mar 26 Javascript
浅谈js键盘事件全面控制
Dec 01 Javascript
jQuery实现获取隐藏div高度的方法示例
Feb 09 Javascript
仿淘宝JSsearch搜索下拉深度用法
Jan 15 Javascript
React Native悬浮按钮组件的示例代码
Apr 05 Javascript
Node.js连接Sql Server 2008及数据层封装详解
Aug 27 Javascript
jQuery 同时获取多个标签的指定内容并储存为数组
Nov 20 jQuery
使用vue实现通过变量动态拼接url
Jul 22 Javascript
vue项目在线上服务器访问失败原因分析
Aug 14 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中ob(Output Buffer 输出缓冲)函数使用方法
2007/07/21 PHP
PHP print类函数使用总结
2010/06/25 PHP
WordPress中邮件的一些修改和自定义技巧
2015/12/15 PHP
Extjs学习笔记之六 面版
2010/01/08 Javascript
JavaScript继承方式实例
2010/10/29 Javascript
window.location.hash 使用说明
2010/11/08 Javascript
jquery多行滚动/向左或向上滚动/响应鼠标实现思路及代码
2013/01/23 Javascript
JS操作Cookie写入和读取实例代码
2013/10/20 Javascript
JavaScript实现带标题的图片轮播特效
2015/05/20 Javascript
在JavaScript的jQuery库中操作AJAX的方法讲解
2015/08/15 Javascript
js实现鼠标点击左上角滑动菜单效果代码
2015/09/06 Javascript
理解javascript定时器中的setTimeout与setInterval
2016/02/23 Javascript
深入理解JavaScript函数参数(推荐)
2016/07/26 Javascript
BootstrapTable请求数据时设置超时(timeout)的方法
2017/01/22 Javascript
vue组件学习教程
2017/09/09 Javascript
jQuery实现图片简单轮播功能示例
2018/08/13 jQuery
js常见遍历操作小结
2019/06/06 Javascript
MockJs结合json-server模拟后台数据
2020/08/26 Javascript
解决Layui 表格自适应高度的问题
2019/11/15 Javascript
python使用webbrowser浏览指定url的方法
2015/04/04 Python
python实现数独算法实例
2015/06/09 Python
python中import reload __import__的区别详解
2017/10/16 Python
sublime python3 输入换行不结束的方法
2018/04/19 Python
python opencv实现图片旋转矩形分割
2018/07/26 Python
python如何判断IP地址合法性
2020/04/05 Python
python 等差数列末项计算方式
2020/05/03 Python
介绍一下javax.servlet.Servlet接口及其主要方法
2015/11/30 面试题
2014年小学国庆节活动方案
2014/09/16 职场文书
信贷客户经理岗位职责
2015/04/09 职场文书
2015年前台接待工作总结
2015/05/04 职场文书
公司考勤管理制度
2015/08/04 职场文书
2016春季校长开学典礼致辞
2015/11/26 职场文书
学生安全责任协议书
2016/03/22 职场文书
2019年个人工作总结范文(3篇)
2019/08/27 职场文书
css position fixed 左右双定位的实现代码
2021/04/29 HTML / CSS
Nginx配置https的实现
2021/11/27 Servers