for...of
是ES6引入用來遍歷全部數據結構的統一方法。前端
這裏的全部數據結構只指具備iterator接口
的數據。一個數據只要部署了 Symbol.iterator
,就具備了 iterator
接口,就可使用 for...of
循環遍歷它的成員。也就是說,for...of循環內部調用的數據結構爲Symbol.iterator
方法。面試
for...of
循環可使用的範圍包括數組、Set 和 Map 結構、某些相似數組的對象(好比arguments
對象、DOM NodeList
對象)、 Generator
對象,以及字符串。也就是說上面提到的這些數據類型原生就具有了 iterator
接口。算法
因此千萬不要錯誤地認爲
for...of
只是用來遍歷數組的。數組
爲何會有 會引入 Iterator
呢,是由於 ES6添加了 Map
, Set
,再加上原有的數組,對象,一共就是4種表示 「集合」的數據結構。沒有 Map
和 Set
以前,咱們都知道 for...in
通常是經常使用來遍歷對象,for
循環 經常使用來遍歷數據,如今引入的 Map
, Set
,難道還要單獨爲他們引入適合用來遍歷各自的方法麼。聰明的你確定能想到,咱們能不能提供一個方法來遍歷全部的數據結構呢,這個方法能遍歷全部的數據結構,必定是這些數據結構要有一些通用的一些特徵,而後這個公共的方法會根據這些通用的特徵去進行遍歷。微信
Iterator
就能夠理解爲是上面咱們所說的通用的特徵。數據結構
咱們來看看官方對 Iterator
是怎麼解釋的:遍歷器(Iterator
)就是這樣一種機制。它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator
接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。通俗點理解就是爲了解決不一樣數據結構遍歷的問題,引入了Iterator
.函數
咱們來模擬實現如下:學習
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{
value: array[nextIndex++],
done: false
}
:
{
value: undefined,
done: true
};
}
};
}
const it = makeIterator(['a', 'b']);
it.next()
// { value: "a", done: false }
it.next()
// { value: "b", done: false }
it.next()
// { value: undefined, done: true }
複製代碼
簡單解釋一下上面
array[nextIndex++]
是什麼意思, 假如nextIndex
當前爲0,則nextIndex++
的意思爲1.返回0 2. 值自增(nextIndex
如今爲1)。以前遇到一道面試題就是考察i++
和++i
ui
let number = 0
console.log(number++)
console.log(++number)
console.log(number)
複製代碼
輸出什麼?this
答案是: 0, 2, 2;
一元后自增運算符 ++:
一元前自增運算符 ++:
結果是 0 2 2.
好了,接着來看 Iterator
的整個的遍歷過程:
it
),指向當前數據的起始位置next
方法,能夠將指針指向數據結構的第一個成員(上面代碼中的a
)。next
方法,能夠將指針指向數據結構的第二個成員(上面代碼中的b
)。next
方法,直到它指向數據結構的結束位置每一次調用next方法,都會返回數據結構的當前成員的信息。具體來講,就是返回一個包含value
和done
兩個屬性的對象。其中,value
屬性是當前成員的值,done
屬性是一個布爾值,表示遍歷是否結束,便是否要有必要再一次調用。
Iterator
的特色
for...of
循環,Iterator
接口主要供for...of
消費部署在 Symbol.iterator
屬性,或者說,一個數據結構只要具備 Symbol.iterator
屬性,就認爲是"可遍歷的"。
原生具有 Iterator 接口的數據結構以下。
Array
Map
Set
String
:字符串是一個相似數組的對象,也原生具備 Iterator 接口。arguments
對象NodeList
對象除了原生具有Iterator
接口的數據以外,其餘數據結構(主要是對象)的 Iterator
接口,都須要本身在Symbol.iterator
屬性上面部署,這樣纔會被for...of
循環遍歷。
對象(Object
)之因此沒有默認部署 Iterator
接口,是由於對象的哪一個屬性先遍歷,哪一個屬性後遍歷是不肯定的,須要開發者手動指定。本質上,遍歷器是一種線性處理,對於任何非線性的數據結構,部署遍歷器接口,就等於部署一種線性轉換。不過,嚴格地說,對象部署遍歷器接口並非很必要,由於這時對象實際上被看成 Map
結構使用,ES5
沒有 Map
結構,而 ES6
原生提供了。
一個對象若是要具有可被for...of
循環調用的 Iterator
接口,就必須在Symbol.iterator
的屬性上部署遍歷器生成方法(原型鏈上的對象具備該方法也可)。
class RangeIterator {
constructor(start, stop) {
this.value = start;
this.stop = stop;
}
[Symbol.iterator]() { return this; }
next() {
let value = this.value;
if (value < this.stop) {
this.value++;
return {
done: false,
value: value
};
}
return {
done: true,
value: undefined
};
}
}
function range(start, stop) {
return new RangeIterator(start, stop);
}
for (let value of range(0, 3)) {
console.log(value); // 0, 1, 2
}
複製代碼
若是Symbol.iterator
方法對應的不是遍歷器生成函數(即會返回一個遍歷器對象),解釋引擎將會報錯。
const obj = {};
obj[Symbol.iterator] = () => 1;
// TypeError: Result of the Symbol.iterator method is not an object
console.log([...obj] )
複製代碼
字符串是一個相似數組的對象,也原生具備 Iterator 接口。
const someString = "hi";
typeof someString[Symbol.iterator]
// "function"
複製代碼
除了 for...of
,還有下面幾個場景
yield*
:yield*
後面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。Array.from()
Map(), Set(), WeakMap(), WeakSet()(好比new Map([['a',1],['b',2]]))
Promise.all()
Promise.race()
看到next
這個你有沒有感到很熟悉,鏈表中 每一個元素由一個存儲元素自己的節點和一個指向下一個元素的引用(即next屬性)組成。是否是很相似,不錯,Iterator
的實現思想就是來源於單向鏈表。
下面來簡單介紹一下單向鏈表。
鏈表存儲有序的元素集合,但不一樣於數組,鏈表中每一個元素在內存中並非連續放置的。每一個元素由一個存儲元素自己的節點和一個指向下一個元素的節點(也稱爲指針或連接)組成,下圖展現了一個鏈表的結構。
和數組相比較,鏈表的一個好處已在於,添加或移除元素的時候不須要移動其餘元素。然而,鏈表須要指針,所以實現鏈表時須要額外注意。數組的另外一個細節是能夠直接訪問任何位置的任何元素,而想要訪問鏈表中間的一個元素,須要從起點(表頭)開始迭代列表知道找到全部元素。
現實生活中也有一些鏈表的例子,好比說尋寶遊戲。你有一條線索,這條線索是指向尋找下一條線索的地點的指針。你順着這條連接去到下一個地點,獲得另外一條指向再下一處的線索,獲得列表中間的線索的惟一辦法,就是從起點(第一條線索)順着列表尋找。
具體怎麼實現一個單向鏈表,這裏就不展開講了,推薦看
《學習JavaScript數據結構與算法》(第二版)
。
關於for...of
的原理,相信你看完上面的內容已經掌握的差很少了,如今咱們以數組爲例,說一下,for...of
和以前咱們常用的其餘循環方式有什麼不一樣。
最原始的寫法就是for
循環。
for (let i = 0; i < myArray.length; index++) {
console.log(myArray[i]);
}
複製代碼
這種寫法比較麻煩,所以數組提供內置的forEach
方法。
myArray.forEach((value) => {
console.log(value);
});
複製代碼
這種寫法的問題在於,沒法中途跳出forEach
循環,break
命令或return
命令都不能奏效。
for...in
循環能夠遍歷數組的鍵名。
const arr = ['red', 'green', 'blue'];
for(let v in arr) {
console.log(v); // '0', '1', '2
}
複製代碼
for...in
循環有幾個缺點:
for...in
循環是以字符串做爲鍵名「0」、「1」、「2」等等。for...in
循環不只遍歷數字鍵名,還會遍歷手動添加的其餘鍵,甚至包括原型鏈上的鍵for...in
循環會以任意順序遍歷鍵名。for...in
循環主要是爲遍歷對象而設計的,不適用於遍歷數組。
for...of
和上面幾種作法(for
循環,forEach
, for...in
)相比,有一些顯著的優勢
for...in
同樣的簡潔語法,可是沒有for...in
那些缺點。forEach
方法,它能夠與break
、continue
和return
配合使用。for...of
能夠用來遍歷全部具備iterator
接口的數據結構。(一個數據結構只要部署了Symbol.iterator
屬性,就被視爲具備 iterator
接口)。也就是說 for...of
循環內部調用是數據結構的 Symbol.iterator
iterator
的實現思想來源於 單向鏈表
forEach
循環中沒法用break
命令或return
命令終止。而for...of
能夠。for...in
遍歷數組遍歷的是鍵名,全部適合遍歷對象,for...of
遍歷數組遍歷的是鍵值。最近發起了一個100天前端進階計劃,主要是深挖每一個知識點背後的原理,歡迎關注 微信公衆號「牧碼的星星」,咱們一塊兒學習,打卡100天。