我常常把 for...in 與 for...of 相互混淆,所以決定寫一篇文章來好好認識他們兩位!
嗨,for...in
for...in 可以用來針對 object 內屬性進行迴圈。
const obj = {
a: 'apple',
b: 'banana',
c: 'cat',
d: 'dog',
e: 'egg',
};
for (let key in obj) {
console.log(key); //a,b,c,d,e
}
看程式碼說故事:
- 先宣告一個 looping variable(如範例中的 key),變數可依自己喜好命名。這個 looping variable 代表著 object 內的 key 值。
- in 後面放上欲進行迴圈的 object(如範例中的 obj)。
- console.log(key) 依序印出 obj 內的 key 值。
你說什麼?你想要的不是 key,而是 value 啊?
簡簡單單~善用 square bracket notation 就能輕鬆辦到囉!
for (const key in obj) {
console.log(obj[key]); //apple, banana, cat, dog, egg
}
基本上,for...in 時取出的 key 值的順序會根據原本物件內屬性的撰寫順序。但凡事總有例外,請看...
const orderedObj = {
'1': 'one',
'10': 'ten',
'5': 'five',
'6': 'six',
'2': 'two',
};
for (let key in orderedObj) {
console.log(key); // 結果竟然是 1,2,5,6,10!!
}
結果出乎意料竟然沒有按照物件內屬性的撰寫順序(1, 10, 5, 6, 2),而是被刻意地「由小到大」排列!
原因就出在 integer properties,它指的是「字串被轉型成整數時保持不變,且反之亦然」。字串 1 轉換成整數數字時為數字 1,字串 10 轉換成整數數字時為數字 10,但字串 1.2 轉換成整數數字則是數字 1,並不屬於 integer property。
簡單來說,當物件內的 key 值是字串整數或是數字整數,for...in 就會貼心地幫你由小到大排列(貼心嗎?我可不這麼認為 😟
for...in 與陣列(但最好別這麼做)
其實 for...in 也可以使用在陣列身上,畢竟陣列也算是物件的一種,取出的就會是陣列的 index。
const arr = ['a', 'b', 'c', 'd', 'e'];
for (let item in arr) {
console.log(item); // 0,1,2,3,4
}
雖然不會報錯,但建議不要這麼做。原因有二:
- for...in 會輸出從 Array.prototype 所繼承的屬性。
除了印出陣列 arr 內的所有 index 之外,也印出了我們在 Array.prototype 加入的屬性 oops。const arr = ['a', 'b', 'c', 'd', 'e']; Array.prototype.oops = 'See you again;)'; for (let item in arr) { console.log(item); // 0,1,2,3,4,oops }
- for...in 當初就是專門為了物件進行迴圈所創,不是陣列。因此使用在陣列上,效能會較差。
要讓陣列跑迴圈還有很多其他更適合的方式!例如我們接下來要介紹的 for...of。
你好,for...of
for...of 用來針對 iterable object (可迭代物件),如陣列,進行迴圈,依序取出元素。
const colorsArr = ['pink', 'yellow', 'black'];
for (let color of colorsArr) {
console.log(color); //pink, yellow, black
}
看程式碼說故事:
- 宣告一個 looping variable(如範例中的 color),變數可依自己喜好命名。這個 looping variable 代表著可迭代物件內的元素。
- of 後面放上欲進行迴圈的 iterable object(如範例中的 colorsArr)。
- console.log(color) 依序印出 colorsArr 內的的元素。
什麼是 iterable object (可迭代物件)?
iterable object 都擁有一個叫做 [@@iterator]()
的 method(其實長這樣:Symbol.iterator
)。除了 Array 之外,String, TypedArray, Map, Set, NodeList, HTMLCollection 都屬於可迭代物件。
for...of 背後做了什麼事
When a for...of loop iterates over an iterable, it first calls the iterable's @@iterator method, which returns an iterator, and then repeatedly calls the resulting iterator's next() method to produce the sequence of values to be assigned to variable. --MDN
執行 for...of 時,發生了下列的事情:
- 呼叫
Symbol.iterator
,而Symbol.iterator
會回傳一個 iterator(迭代器)。 - 接著,這個迭代器會不斷地重複呼叫一個迭代器內內建的方法,叫做 next()。
- next() 會回傳一個物件,長得像這樣
{ value: "XXX", done: true }
,重點在於它會有 value 和 done 兩個屬性。- done: 布林值,true 代表說已經迭代到底,沒有下一個值可以輸出了,false 則表示還有值可以繼續輸出。
- value: 迭代器所回傳的值,這個值會被賦予給我們在 for...of 裡宣告的 looping variable。
覺得好虛幻嗎?我們實際把它印出來看看吧!
const colorsArr = ['pink', 'yellow', 'black'];
const iterator = colorsArr[Symbol.iterator](); //把迭代器儲存在變數中
//手動呼叫迭代器的 next() method
console.log(iterator.next()); //{ value: 'pink', done: false }
console.log(iterator.next()); //{ value: 'yellow', done: false }
console.log(iterator.next()); //{ value: 'black', done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
在 done 為 true 的時候就代表迭代器已經完成工作了,因此也停止了 for...of 迴圈。
for...of 與物件
物件不是可迭代物件(物件沒有 [@@iterator]()
method),因此 for...of 是無法應用在物件上的,會報錯。
// ☠️☠️☠️ 錯誤用法 ☠️☠️☠️
const obj = {
a: 'apple',
b: 'banana',
c: 'cat'
};
for (let item of obj) {
console.log(item);
}
// TypeError: obj is not iterable
雖然物件不是可迭代物件,但既然我們已經知道可迭代物件的魔法就在於 Symbol.iterator
,我們是不是就能把物件變成可迭代物件,就能使用 for...of 了呢?
欸...說的是沒有錯啦...(捲袖子捲捲捲)那就來試試看吧!
我們現在有一個物件長這樣:let range = {from: 1, to: 5},希望可以把 range 變成可迭代物件,使用 for...of 依序印出 1, 2, 3, 4, 5。
const range = {
from: 1,
to: 5,
[Symbol.iterator]: function () {
this.current = this.from;
return {
next: () => ({
done: this.current > this.to,
value: this.current++,
}),
};
},
};
for (let value of range) {
console.log(value); //見證奇蹟的時刻:1,2,3,4,5 🎉
}
看程式碼說故事:
- 在 range 物件內加入 [Symbol.iterator] method。
- 我們在 range 裡面新增一個屬性 current,用來記錄迭代器回傳的值。
- [Symbol.iterator] 回傳一個物件,也就是迭代器,當中包含 next() method。
- next() 回傳一個物件,內含有 done 和 value 兩個屬性。
4.1 done: 當 current 大於 range.to 就代表已經迭代到底了
4.2 value: 每一次執行 next() 都替 current + 1,來達到我們要從數字 to 依序列印到數字 from 的目標
重點回顧
for...in | for...of | |
---|---|---|
使用對象 | 物件(陣列也 OK) | 可迭代物件(如 Array, Map, Set, String) |
做什麼 | 取得物件的 key | 取得可迭代物件的 value |
注意事項 | 使用在陣列時,會取出 index | 物件不是可迭代物件,故無法使用 |
還在努力學習中,如有錯誤請不吝指正 🙇🏻♀️
參考資料
https://javascript.info/object#forin
https://javascript.info/array#loops
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of