本文從使用 forEach
對數組進行遍歷開始提及,粗略對比使用 forEach
, for...in
, for...of
進行遍歷的差別,並由此引入 ES6 中 *可迭代對象/迭代器 *的概念,並對其進行粗略介紹。javascript
forEach
方法按升序爲數組中的有效值的每一項執行一次callback
函數html
empty
的內容。(undefined
不會被跳過哦)。forEach
的第二個參數 thisArg
傳入後能夠改變 callback
函數的 this
值,若是不傳則爲 undefined
。固然, callback
所拿到的 this
值也受廣泛規律的支配:這意味着若是 callback
是個箭頭函數,則 thisArg
會被忽略。(由於箭頭函數已經在詞法上綁定了this值,不能再改了)forEach
遍歷的範圍在第一次調用 callback
前就會肯定,這意味着調用forEach
後添加到數組中的項不會被 callback
訪問到。同時,因爲 forEach
的遍歷是基於下標的(能夠這麼理解,並能從 Polyfill 中看到這一實現),那麼在循環中刪了數組幾項內容則會有奇怪的事情發生:好比下圖中,在下標爲 1 的時候執行了 shift()
,那麼原來的第 3 項變爲了第 2 項,原來的第 2 項則被跳過了。for
/ for..of
Array.prototype.every()
/ Array.prototype.some()
並返回 false
/ true
來進行中斷/跳出undefined
)for...in
循環實際是爲循環」enumerable「對象而設計的:java
for...in
語句以任意順序遍歷一個對象的 可枚舉屬性 。對於每一個不一樣的屬性,語句都會被執行。es6
for...in
循環將遍歷對象自己的全部可枚舉屬性,以及對象從其構造函數原型中繼承的屬性(更接近原型鏈中對象的屬性覆蓋原型屬性)。for...in
的同時還須要配合 getOwnPropertyNames()
或 hasOwnProperty()
for ... in
將以任何特定的順序返回索引,好比在 IE 下可能會亂來。Array
:
for...of
的本質是在 可迭代對象(iterable objects) 上調用其 迭代方法 建立一個迭代循環,並執行對應語句。能夠迭代 數組/字符串/類型化數組(TypedArray)/Map/Set/generators/類數組對象(NodeList/arguments) 等。須要注意的是, Object
並非一個可迭代對象。數組
for...of
是 ES6 中新出爐的,其彌補了 forEach
和 for...in
的諸多缺點:瀏覽器
break
, throw
或return
等終止那麼咱們簡單的幾句話說明一下 for...of
和 for...in
的區別:數據結構
for...of
語句遍歷 可迭代對象 定義要迭代的數據。for...of
獲得的是 值(value), 而 for...in
獲得的是 鍵(key)。那麼扯到了 可迭代對象 ,就不得不說說 ES6 中新增的與 可迭代對象/迭代器 有關東西了。函數
*iteration *是 ES6 中新引入的遍歷數據的機制,其核心概念是:iterable(可迭代對象) 和 iterator(迭代器):ui
Symbol.iterator
方法首先,可迭代協議容許 JavaScript 對象去定義或定製它們的迭代行爲。而如 Array
或 Map
等內置可迭代對象有默認的迭代行爲,而如 Object
則沒有。(因此爲何不能對 Object
用 for...of
)再具體一點,即對象或其原型上有 [Symbol.iterator]
的方法,用於返回一個對象的無參函數,被返回對象符合迭代器協議。而後在該對象被迭代的時候,調用其 [Symbol.iterator]
方法得到一個在迭代中使用的迭代器。this
首先能夠看見的是 Array 和 Map 在原型鏈上有該方法,而 Object 則沒有。這印證了上面對於哪些能夠用於 for...of
的說法。
若是咱們非要想用 for...of
對 Object
所擁有的屬性進行遍歷,則可以使用內置的 Object.keys()
方法:
for (const key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
複製代碼
或者若是你想要更簡單得同時獲得鍵和值,能夠考慮使用 Object.entries()
:
for (const [key, value] of Object.entries(someObject)) {
console.log(key + ": " + value);
}
複製代碼
其次,有以下狀況會使用可迭代對象的迭代行爲:
for...of
上文說到返回了一個迭代器用於迭代,那咱們就來看看符合什麼樣規範的纔算一個 迭代器 。 只須要實現一個符合以下要求的 next
方法的對象便可:
屬性
|
值
|
next
|
返回一個對象的無參函數,被返回對象擁有兩個屬性:
|
本質上,在使用一個迭代器的時候,會不斷調用其 next()
方法直到返回 done: true
。
既然符合可迭代協議的均爲可迭代對象,那接下來就簡單自定義一下迭代行爲:
// 讓咱們的數組倒序輸出 value
const myArr = [1, 2, 3];
myArr[Symbol.iterator] = function () {
const that = this;
let index = that.length;
return {
next: function () {
if (index > 0) {
index--;
return {
value: that[index],
done: false
};
} else {
return {
done: true
};
}
},
};
};
[...myArr]; // [3, 2, 1]
Array.from(myArr) // [3, 2, 1]
複製代碼
當一個__可迭代對象__須要被迭代的時候,它的 Symbol.iterator
方法被無參調用,而後返回一個用於在迭代中得到值的迭代器。 換句話說,一個對象(或其原型)上有符合標準的 Symbol.iterator
接口,那他就是 可迭代的(Iterator)
,調用這個接口返回的對象就是一個 迭代器
上文提到說 for...of
比 forEach
好在其能夠被「中斷」,那麼對於在 for...of
中中斷迭代,其本質是中斷了迭代器,迭代器在中斷後會被關閉。說到這裏,就繼續說一下迭代器關閉的狀況了。
首先,迭代器的關閉分爲兩種狀況:
next()
方法直到返回 done: true
,也就是迭代器正常執行完後關閉return()
方法來告訴迭代器不打算再調用 next()
方法那麼何時會調用迭代器的 return
方法呢:
return()
是個可選方法,只有具備該方法的迭代器纔是 可關閉的(closable)return()
,如 break
, throw
或 return
等最後,return()
方法中也不是想怎麼寫就怎麼寫的,也有本身的要求, return()
方法須要符合如下規範:
return(x)
一般應該返回如 { done: true, value: x }
的結果,若是返回的不是個對象則會報錯return()
後, next()
返回的對象也應該是 done:true
(這就是爲何有一些迭代器在 for...of
循環中中斷後沒法再次使用的緣由,好比 Generator
)同時,須要額外注意的是,及時在收到迭代器最後一個值後調用 break
等,也會觸發 return()
function createIterable() {
let done = false;
const iterable = {
[Symbol.iterator]() {
return this;
},
next() {
if (!done) {
done = true;
return {
done: false,
value: 'a'
};
} else {
return {
done: true,
value: undefined
};
}
},
return () {
console.log('return() was called!');
return {
done: true,
value: undefined
};
},
};
return iterable;
}
for (const value of createIterable()) {
console.log(value);
break;
}
複製代碼
上文 迭代器協議 中提到的返回的擁有 next()
方法的對象和咱們在 Generator
中使用的 next()
方法彷佛如出一轍。確實, Generator
符合可迭代協議和迭代器協議的。
由於 Generator
既有符合規範的 next()
(迭代器協議)方法,也有 Symbol.iterator
(可迭代協議)方法,所以它 既是迭代器也是可迭代對象 。
默認狀況下,Generator對象是可關閉的。所以在用 for...of
時中斷迭代後,沒法再次對原有 Generator對象進行迭代。(由於調用return()
後, next()
返回的對象也應該是 done:true
)
固然,既然是默認狀況,咱們就能夠想辦法讓其沒法被關閉: 能夠經過包裝一下迭代器,將迭代器自己/原型上的 return()
方法被重寫掉
class PreventReturn {
constructor(iterator) {
this.iterator = iterator;
}
[Symbol.iterator]() {
return this;
}
next() {
return this.iterator.next();
}
// 重寫掉 return 方法
return (value = undefined) {
return {
done: false,
value
};
}
}
複製代碼
更多關於 Generator
的內容就不在本篇進行闡述,有機會將單獨做爲一篇慢慢講。