在JavaScript
的世界中,咱们可使用不少种循环表达式:javascript
while
表达式do...while
表达式for
表达式for...in
表达式for...of
表达式全部这些表达式都有一个基本的功能:它们会重复一件事情直到一个具体的条件出现。java
在这篇文章中,咱们将深刻 for...of
表达式,去了解它是如何工做的,以及在咱们的应用中可使用它来优化代码的地方。node
for...of
是一种 for
表达式,用来迭代 iterables(iterable objects)
直到终止条件出现。数组
下面是一个基础的例子:app
let arr = [2,4,6,8,10]
for(let a of arr) {
log(a)
}
// It logs:
// 2
// 4
// 6
// 8
// 10
复制代码
使用比for
循环更好的代码,咱们遍历了arr
数组。异步
let myname = "Nnamdi Chidume"
for (let a of myname) {
log(a)
}
// It logs:
// N
// n
// a
// m
// d
// i
//
// C
// h
// i
// d
// u
// m
// e
复制代码
你知道若是咱们使用for
循环,咱们将必须使用数学和逻辑去判断什么时候咱们将会达到myname
的末尾而且中止循环。可是正如你所见的,使用for...of
循环以后,咱们将会避免这些烦人的事情。async
for...of
有如下通用的使用方法:函数
for ( variable of iterable) {
//...
}
复制代码
variable
- 保存每次迭代过程当中的迭代对象的属性值 iterable
- 咱们进行迭代的对象oop
在for...of
循环的定义中,咱们说它是“迭代 iterables(iterable objects)”。这个定义告诉咱们for...of
循环只能用于可迭代对象。测试
那么, 什么是可迭代的对象(iterables
)?
简单来讲的话,可迭代对象(Iterables)是能够用于迭代的对象。在ES6
中增长了几个特性。这些特性包含了新的协议,其中就有迭代器(Iterator)协议和可迭代(Iterable)协议。
参考MDN
的描述,“可迭代协议容许JS
对象定义或者修改它们的迭代行为,好比哪些值能够被for...of
循环到。”同时“为了变成可迭代的,对象必须实现@@iterator
方法,这意味着这个对象(或者其原型链上的对象)必须包含一个可使用Symbol.iterator
常量访问的@@iterator
的属性”
说人话就是,对于可使用for...of
循环的对象来讲,它必须是可迭代的,换句话就是它必须有@@iterator
属性。这就是可迭代协议。
因此当对象有@@iterator
属性的时候就能够被for...of
循环迭代,@@iterator
方法被for...of
调用,而且返回一个迭代器。
同时,迭代器协议定义了一种对象中的值如何返回的方式。一个迭代器对象必须实现next
方法,next 方法须要遵循如下准则:
举个例子:
const createIterator = function () {
var array = ['Nnamdi','Chidume']
return {
next: function() {
if(this.index == 0) {
this.index++
return { value: array[this.index], done: false }
}
if(this.index == 1) {
return { value: array[this.index], done: true }
}
},
index: 0
}
}
const iterator = createIterator()
log(iterator.next()) // Nnamdi
log(iterator.next()) // Chidume
复制代码
基本上,@@iterator
方法回返回一个迭代器,for...of
循环正是使用这个迭代器去循环操做目标对象从而获得值。所以,若是一个对象没有@@iterator
方法同时这个返回值是一个迭代器,for...of
循环将不会生效。
const nonIterable = //...
for( let a of nonIterable) {
// ...
}
for( let a of nonIterable) {
^
TypeError: nonIterable is not iterable
复制代码
内置的可迭代对象有有如下这些:
注意,Object 不是可迭代的。若是咱们尝试使用for...of
去迭代对象的属性:
let obj {
firstname: "Nnamdi",
surname: "Chidume"
}
for(const a of obj) {
log(a)
}
复制代码
将会抛出一个异常:
for(const a of obj) {
^
TypeError: obj is not iterable
复制代码
咱们能够用下面的方式检查一个对象是否可迭代:
const str = new String('Chidume');
log(typeof str[Symbol.iterator]);
function 复制代码
看到了吧,打印的结果是function
, 这意味着@@iterator
属性存在,而且是函数类型。若是咱们在 Object 上面进行尝试:
const obj = {
surname: "Chidume"
}
log(typeof obj[Symbol.iterator]);
undefined
复制代码
哇!undefined
表示不存在。
数组是可迭代对象。
log(typeof new Array("Nnamdi", "Chidume")[Symbol.iterator]);
// function
复制代码
这是咱们能够对它使用for...of
循环的缘由。
const arr = ["Chidume", "Nnamdi", "loves", "JS"]
for(const a of arr) {
log(a)
}
// It logs:
// Chidume
// Nnamdi
// loves
// JS
const arr = new Array("Chidume", "Nnamdi", "loves", "JS")
for(const a of arr) {
log(a)
}
// It logs:
// Chidume
// Nnamdi
// loves
// JS
复制代码
字符串也是可迭代的。
const myname = "Chidume Nnamdi"
for(const a of myname) {
log(a)
}
// It logs:
// C
// h
// i
// d
// u
// m
// e
//
// N
// n
// a
// m
// d
// i
const str = new String("The Young")
for(const a of str) {
log(a)
}
// 打印结果是:
// T
// h
// e
//
// Y
// o
// u
// n
// g
复制代码
const map = new Map([["surname", "Chidume"],["firstname","Nnamdi"]])
for(const a of map) {
log(a)
}
// 打印结果是:
// ["surname", "Chidume"]
// ["firstname","Nnamdi"]
for(const [key, value] of map) {
log(`key: ${key}, value: ${value}`)
}
// 打印结果是:
// key: surname, value: Chidume
// key: firstname, value: Nnamdi
复制代码
const set = new Set(["Chidume","Nnamdi"])
for(const a of set) {
log(a)
}
// 打印结果是:
// Chidume
// Nnamdi
复制代码
const typedarray = new Uint8Array([0xe8, 0xb4, 0xf8, 0xaa]);
for (const a of typedarray) {
log(a);
}
// 打印结果是:
// 232
// 180
// 248
// 170
复制代码
arguments对象是可遍历的吗?咱们先来看:
// testFunc.js
function testFunc(arg) {
log(typeof arguments[Symbol.iterator])
}
testFunc()
$ node testFunc
function 复制代码
答案出来了。若是进一步探讨,arguments实际上是IArguments类型的对象,并且实现了IArguments接口的class都有一个@@iterator属性,使得arguments对象可遍历。
// testFunc.js
function testFunc(arg) {
log(typeof arguments[Symbol.iterator])
for(const a of arguments) {
log(a)
}
}
testFunc("Chidume")
// It:
// Chidume
复制代码
正如上一节那样,咱们能够建立一个自定义的能够经过for..of
遍历的可遍历对象。
var obj = {}
obj[Symbol.iterator] = function() {
var array = ["Chidume", "Nnamdi"]
return {
next: function() {
let value = null
if (this.index == 0) {
value = array[this.index]
this.index++
return { value, done: false }
}
if (this.index == 1) {
value = array[this.index]
this.index++
return { value, done: false }
}
if (this.index == 2) {
return { done: true }
}
},
index: 0
}
};
复制代码
这里建立了一个可遍历的obj
对象,经过[Symbol.iterator]
赋予它一个@@iterator
属性,而后建立一个返回遍历器的方法。
//...
return {
next: function() {...}
}
//...
复制代码
记住,遍历器必定要有一个next()
方法。
在next方法里面,咱们实现了在for...of遍历过程当中会返回的值,这个过程很清晰。
Let’s test this our obj
against a for..of to see what will happen:
// customIterableTest.js
//...
for (let a of obj) {
log(a)
}
$ node customIterableTest
Chidume
Nnamdi
复制代码
耶!成功了!
简单对象是不可遍历的,经过Object
获得的对象也是不可遍历的。
咱们能够经过自定义遍历器把@@iterator添加到Object.prototype来实现这个目标。
Object.prototype[Symbol.iterator] = function() {
let properties = Object.keys(this)
let count = 0
let isdone = false
let next = () => {
let value = this[properties[count]]
if (count == properties.length) {
isdone = true
}
count++
return { done: isdone, value }
}
return { next }
}
复制代码
properties
变量里面包含了经过调用Object.keys()
获得的object的全部属性。在next方法里面,咱们只须要返回properties里面的每个值,而且经过更新做为索引的count变量来获取下一个值。当count达到properties的长度的时候,就把done设为true,遍历就结束了。
用Object来测试一下:
let o = new Object()
o.s = "SK"
o.me = 'SKODA'
for (let a of o) {
log(a)
}
SK
SKODA
复制代码
成功了!!!
用简单对象来测试一下:
let dd = {
shit: 900,
opp: 800
}
for (let a of dd) {
log(a)
}
900
800
复制代码
也成功了!! :)
因此咱们能够把这个添加到polyfill里面,而后就能够在app里面使用for...of来遍历对象了。
咱们能够用for...of来遍历class的实例中的数据列表。
class Profiles {
constructor(profiles) {
this.profiles = profiles
}
}
const profiles = new Profiles([
{
firstname: "Nnamdi",
surname: "Chidume"
},
{
firstname: "Philip",
surname: "David"
}
])
复制代码
Profiles类有一个profile
属性,包含一个用户列表。当咱们须要在app中用for...of来展现这个列表的时候,若是这样作:
//...
for(const a of profiles) {
log(a)
}
复制代码
显然是不行的。
for(const a of profiles) {
^
TypeError: profiles is not iterable
复制代码
为了把profiles
变成可遍历,请记住如下规则:
@@iterator
属性。@@iterator
的方法必定要返回一个遍历器(iterator).iterator
必定要实现next()
方法。咱们经过一个熟悉的常量[Symbol.iterator]
来定义这个@@iterator
class Profiles {
constructor(profiles) {
this.profiles = profiles
}
[Symbol.iterator]() {
let props = this.profiles
let propsLen = this.profiles.length
let count = 0
return {
next: function() {
if (count < propsLen) {
return { value: props[count++], done: false }
}
if (count == propsLen) {
return { done: true }
}
}
}
}
}
复制代码
而后,若是咱们这样运行:
//...
for(const a of profiles) {
log(a)
}
$ node profile.js
{ firstname: 'Nnamdi', surname: 'Chidume' }
{ firstname: 'Philip', surname: 'David' }
复制代码
咱们能够显示 profiles 的属性
ECMAScript 2018 引入了一个新的语法,能够循环遍历一个 Promise 数组,它就是 for-await-of
和新的 Symbol Symbol.asyncIterator
。
iterable 中的 Symbol.asyncIterator
函数须要返回一个返回 Promise 的迭代器。
const f = {
[Symbol.asyncIterator]() {
return new Promise(...)
}
}
复制代码
[Symbol.iterator]
和 [Symbol.asyncIterator]
的区别在于前者返回的是 { value, done }
然后者返回的是一个 Promise
,只不过当 Promise resolve 的时候传入了 { value, done }
。
咱们上面的那个 f 例子将以下所示:
const f = {
[Symbol.asyncIterator]() {
return {
next: function() {
if (this.index == 0) {
this.index++
return new Promise(res => res({ value: 900, done: false }))
}
return new Promise(res => res({ value: 1900, done: true }))
},
index: 0
}
}
}
复制代码
这个 f
是可异步迭代的,你看它老是返回一个 Promise ,而只有在迭代时 Promise 的 resolve 才返回真正的值。
要遍历 f
,咱们不能使用 for..of
而是要使用新的 for-await-of
,它看起来会是这样:
// ...
async function fAsyncLoop(){
for await (const _f of f) {
log(_f)
}
}
fAsyncLoop()
$ node fAsyncLoop.js
900
复制代码
咱们也可使用 for-await-of
来循环遍历一个 Promise 数组:
const arrayOfPromises = [
new Promise(res => res("Nnamdi")),
new Promise(res => res("Chidume"))
]
async function arrayOfPromisesLoop(){
for await (const p of arrayOfPromises) {
log(p)
}
}
arrayOfPromisesLoop()
$ node arrayOfPromisesLoop.js
Nnamdi
Chidume
复制代码
在这篇文章中咱们深刻研究了 for...of
循环,咱们首先定义理解什么是 for...of
,而后看看什么是可迭代的。for...of
为咱们节省了许多复杂性和逻辑,并有助于使咱们的代码看起来很清晰易读,若是你尚未尝试过,我认为如今正是时候。