【JS學習筆記】認識 for…in 與他的兄弟 for…of
我常常把 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 所繼承的屬性。
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
}
除了印出陣列 arr 內的所有 index 之外,也印出了我們在 Array.prototype 加入的屬性 oops。
2. 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 }java
在 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 兩個屬性。
・done: 當 current 大於 range.to 就代表已經迭代到底了
・value: 每一次執行next()
都替 current + 1,來達到我們要從數字 to 依序列印到數字 from 的目標
重點回顧
還在努力學習中,如有錯誤請不吝指正 🙇🏻♀️
參考資料
Originally published at https://annie-learning-journal.coderbridge.io.