# Javascript的for in及for of介紹

photo by [Tine Ivanič](https://unsplash.com/@tine999) on [unsplash](https://unsplash.com/photos/u2d0BPZFXOY)
>If you cannot explain what you are attempting to accomplish to a sixyear-old child, then you are probably not clear about it yourself. -Albert Einstein
---
## 前言
Javascript裡的for迴圈家族,不外乎就是for、forEach、for...in以及for...of,這幾個兄弟了,而今天我們要針對for...in和for...of做深入的介紹。
<br/>
## for in迴圈基本介紹
for in這個語法是在ES5就已經出現的語法,而他可以針對物件(Object)以及陣列(Array)做遍歷,來看看以下的程式碼範例。
```
//針對物件:
let jason = {
firstName: 'Jason',
lastName: 'Tung'
}
for (let prop in jason) {
console.log(prop + ': ' + jason[prop]);
}
// firstName: Jason
// lastName: Tung
//針對陣列:
let arr = [10, 202, 383];
for (let ele in arr) {
console.log(ele);
}
// 0
// 1
// 2
```
首先在物件的範例中,我們有一個名為Jason的物件,接著我們透過自定義的prop,存取物件裡的屬性(property),而for in迴圈會遍歷物件裡所有的屬性,直到所有屬性都遍歷完成,所以在這個範例,他會遍歷兩次,把firstName和lastName都console出來;在陣列的範例中,for in迴圈則會把陣列裡的所有元素(element)的索引值(index)給遍歷出來。
## for in迴圈奇怪的部分
接著我們在剛剛的物件和陣列增加一個屬性foo,value帶上bar,迴圈依然正常的輸出。
```
//針對物件:
jason.foo = 'bar';
for (let prop in jason) {
console.log(prop + ':' + jason[prop]);
}
// firstName:Jason
// lastName:Tung
// foo:bar
//針對陣列:
arr.foo = 'bar';
for (let ele in arr) {
console.log(ele);
}
// 0
// 1
// 2
// foo
```
### 1. 物件Prototype繼承之問題
而如果我們是透過函式建構式(function constructor)來創造物件時,物件可能會繼承含是建構式的一些屬性和方法,這時如果直接用for in迴圈時,繼承的屬性和方法也會一起被輸出。
```
// function constructor,建構一個Person物件
let Person = function (firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// function constructor 的 prototype
Person.prototype.getFullName = function () {
return this.firstName + ' ' + this.lastName;
}
// function constructor建立的物件 Person1
let Person1 = new Person('Jason', 'Tung');
// for...in輸出
for (let prop in Person1) {
console.log(prop + ': ' + Person1[prop]);
}
// firstName: Jason
// lastName: Tung
// getFullName:function(){return this.firstName + ' ' + this.lastName;}
```
可以發現在prototype裡繼承的方法也被輸出了。
:::warning
:zap: 注意:關於prototype的觀念,可以參考[Huli寫的文章](https://blog.techbridge.cc/2017/04/22/javascript-prototype/),這是一個超大坑。
:::
為了避免此問題,網路上有一種通用解法,使用Object.prototype.hasOwnProperty,透過這個方法可以檢查這個屬性是否是直接或是繼承的,他會回傳一個布林值,當是true的時候,表該屬性是直接的。
```
for (let prop in Person1) {
if (Person1.hasOwnProperty(prop)) {
console.log(prop + ': ' + Person1[prop]);
}
}
// firstName: Jason
// lastName: Tung
```
當然還有一種方法,就是用Object.keys(),但是他會回傳一個陣列,裡面的元素這個物件會回傳一個陣列,裡面的元素會是這個物件裡的所有屬性,且不包含繼承的屬性;同樣的,如果你要取物件裡所有的值,也可以使用Object.values(),這個方法會回傳一個陣列,裡面的元素會是這個物件裡的所有值,且也不包含繼承下來的值。
```
let keyArr = Oject.keys(Person1);
console.log(keyArr);
// ['firstName', 'lastName']
let valueArr = Object.values(Person1);
console.log(valueArr);
// ['Jason', 'Tung']
// 輸出key value
for (let i = 0; i < keyArr.length; i++) {
console.log(keyArr[i] + ': ' + Person1[keyArr[i]]);
}
// firstName: Jason
// lastName: Tung
```
### 2. 陣列Prototype繼承問題
同樣的,陣列在使用for in迴圈時,也有上述會把繼承的方法或屬性輸出的情況。
```
Array.prototype.job = 'front-end developer';
let arr1 = ['cat', 'dog', 'human'];
for (let prop in arr1) {
console.log(prop + ': ' + arr1[prop])
}
// 0: cat
// 1: dog
// 2: human
// job: front-end developer
```
為了避免此問題發生,我們可以直接用一般的for迴圈就可以了。不過我們也可以直接用for of迴圈和Object.entries(),for of迴圈稍後會再詳細介紹一下。
```
// 一般for迴圈
for (let i = 0; i < arr1.length; i++) {
console.log(i + ': ' + arr1[i]);
}
// 0: cat
// 1: dog
// 2: human
for (let [key, value] of Object.entries(arr1)) {
console.log(key + ": " + value);
}
// 0: cat
// 1: dog
// 2: human
```
---
### 小結
所以根據以上for in奇怪的部分,我們在處理有繼承屬性的物件或陣列時,應該避免去使用for in迴圈,以防產生預期之外的狀況。取而代之的是,你可以好好利用Object.keys()、Object.values()、Object.entries()結合一般的for迴圈處理物件;至於陣列的處理,就輪到我們接下來要講的for of迴圈。
<br />
## for of迴圈基本介紹
終於,ES6裡面出現了for of迴圈的語法,它主要彌補了for迴圈家族的不足處,你可以利用它來處理迭代(iterable)的資料,像是陣列、字串、set、map、型別陣列等等。
看以下範例:
```
// 陣列的for of
let arr = [1, 2, 3];
for(let value of arr){
console.log(value);
}
// 1
// 2
// 3
//字串的for of
let name = 'Jason';
for(let value of name){
console.log(value);
}
// J
// a
// s
// o
// n
//set的for of
let set = new Set([1, 2, 3, 4, 5]);
for(let value of set){
console.log(value);
}
// 1
// 2
// 3
// 4
// 5
//map的for of
let map = new Map();
map.set('name', 'Jason');
map.set('job', 'engineer');
for (var [key, value] of map) {
console.log(key + ': ' + value);
}
// name: Jason
// job: engineer
```
### 小結
剛剛也說到for of補足了以往for迴圈家族的一些缺點,例如:forEach無法使用continue、break、return等等語句,但for of可以支持;for of也解決上面所講的繼承問題;for of的語法也比一般for迴圈寫法更加簡潔。而且最棒的是他支援相當多的迭代類型資料。
所以當你以後遇到要處理迭代資料且需要用到迴圈時,不妨可以試試for of迴圈。