在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
爲咱們節省了許多複雜性和邏輯,並有助於使咱們的代碼看起來很清晰易讀,若是你尚未嘗試過,我認爲如今正是時候。