【JS學習筆記】認識 for…in 與他的兄弟 for…of

Annie Chien
9 min readDec 10, 2022

--

我常常把 for…in 與 for…of 相互混淆,所以決定寫一篇文章來好好認識他們兩位!

Photo by Alexandru Acea on Unsplash

嗨,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
}

看程式碼說故事:

  1. 先宣告一個 looping variable(如範例中的 key),變數可依自己喜好命名。這個 looping variable 代表著 object 內的 key 值。
  2. in 後面放上欲進行迴圈的 object(如範例中的 obj)。
  3. 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
}

雖然不會報錯,但建議不要這麼做。原因有二:

  1. 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
}

看程式碼說故事:

  1. 宣告一個 looping variable(如範例中的 color),變數可依自己喜好命名。這個 looping variable 代表著可迭代物件內的元素。
  2. of 後面放上欲進行迴圈的 iterable object(如範例中的 colorsArr)。
  3. 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 時,發生了下列的事情:

  1. 呼叫 Symbol.iterator,而 Symbol.iterator 會回傳一個 iterator(迭代器)。
  2. 接著,這個迭代器會不斷地重複呼叫一個迭代器內內建的方法,叫做 next()。
  3. 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 🎉
}

看程式碼說故事:

  1. 在 range 物件內加入 [Symbol.iterator] method
  2. 我們在 range 裡面新增一個屬性 current,用來記錄迭代器回傳的值。
  3. [Symbol.iterator] 回傳一個物件,也就是迭代器,當中包含 next() method。
  4. next() 回傳一個物件,內含有 done 和 value 兩個屬性。
    ・done: 當 current 大於 range.to 就代表已經迭代到底了
    ・value: 每一次執行 next() 都替 current + 1,來達到我們要從數字 to 依序列印到數字 from 的目標

重點回顧

還在努力學習中,如有錯誤請不吝指正 🙇🏻‍♀️

參考資料

--

--