---
# System prepended metadata

title: What language constructions do you use for iterating over object properties and array items? (會使用甚麼方法來遍歷物件屬性跟陣列)

---

# What language constructions do you use for iterating over object properties and array items? (會使用甚麼方法來遍歷物件屬性跟陣列)


## 前情提要
### JavaScript 物件屬性的特性
我們對物件屬性做 新增，修改，刪除，是靠著屬性內部有更細緻的『特性』，在預設情況下這些屬性的『三大特性』設定開啟，才可以如此輕鬆操作。
```+
let person = { name: 'derek' };
person.name;             // READ
person.age = 28;         // CREATE
person.name = 'DEREK';   // UPDATE
delete person.age;       // DELETE
```

**💡Enumerable: 可以用 Object.keys(person) 將他們『列舉』出來 (READ)
💡Writable: 可以對屬性做『修改』(UPDATE)
💡Configurable: 有權限調整屬性的上述兩個特性。此外也可以用 delete 來移除某屬性（DELETE）**
[三大特性介紹](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)

### Object.getOwnPropertyNames()& Object.keys() & Object.values() & Object.entries()

**1.Object.keys()**
* 一般來說我們關注的屬性皆為可列舉屬性(Enumerable)，開發上常用 `Object.keys()`來遍歷。
```+
let person = { name: 'derek' };
person.sex = 'male';
person['color'] = 'Red';

console.log(Object.keys(person)); // ['name', 'sex', 'color']

```
**2.Object.getOwnPropertyNames()**
* `Object.getOwnPropertyNames()` 會列出所有非繼承而來的屬性鍵，不論是否為 Enumerable

```+
let person = { name: 'derek' };

// 利用Object.defineProperty(obj, prop, descriptor)定義物件屬性
Object.defineProperty(person, 'age', { enumerable: false }); 
person.age = 28;

console.log(Object.getOwnPropertyNames(person)); // [ 'name', 'age']
console.log(Object.keys(person));  // [ 'name' ]
```
**3.Object.values()**
* `Object.values()` 取得 property value，並以陣列回傳。
```+
let object = {a: 1, b: 2, c: 3};

console.log(Object.values(object));  // [1, 2, 3]

```
**4.Object.entries()**
* `Object.entries()` 取得 property 的 key 和 value，並以陣列回傳。
```+
let object = {a: 1, b: 2, c: 3};

console.log(Object.entries(object));
// [
//   ["a", 1],
//   ["b", 2],
//   ["c", 3]
// ]
```
### 表格:

|                            | 回傳屬性的         | 顯示不可列舉屬性 | 版本 |
| -------------------------- | ------------------ | ------------------ | ---- |
| Object.getOwnPropertyNames | keys               | 是                 | ES5  |
| Object.keys()              | keys               | 否                 | ES5  |
| Object.values()            | values             |  否                  | ES8  |
| Object.entries()           | [key, value] pairs |    否                | ES8  |



## JavaScript 常見的遍歷方法
1. for (let index = 0; index < array.length; index += 1) {}
1. for (const key in object) {}
1. for (const interator of object) {}
1. array.forEach((item, index, array) => {} )

### **1.for**
作用對象：陣列

```+
let arr = ['Eric', 'Allen', 'Owen'];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]); // Eric 、 Allen 、 Owen
}
```
for 迴圈非常實用，但若只用來遍歷陣列時，與其他方法相比較冗長。

### **2.for...in**
作用對象：物件、陣列(不建議) 
遍歷對象：鍵(key)

```+
let arr = ['Eric', 'Allen', 'Owen'];
//遍歷陣列
for (const key in arr) {
  console.log(key); // 0 、 1 、 2
}

let obj = {
  name: 'Danny',
  age: 26,
  height: 180,
  weight: 72,
};
//遍歷物件
for (const key in obj) {
  console.log(obj[key]); // Danny 、 26 、 180 、 72
}

```
### **3.for...of**
作用對象：陣列
遍歷對象：值(value)

```+
let arr = ['Eric', 'Allen', 'Owen'];

//遍歷陣列
for (const value of arr) {
  console.log(value); // Eric 、 Allen 、 Owen
}

//遍歷陣列：搭配Array.prototype.entries()- ES6 新增
for (const iterator of arr.entries()) {
  console.log(iterator); // [ 0, 'Eric' ] 、 [ 1, 'Allen' ] 、 [ 2, 'Owen' ]
}

let obj = {
  name: 'Danny',
  age: 26,
  height: 180,
  weight: 72,
}; 

//遍歷物件：搭配 Object.values() - ES8 新增
for (const value of Object.values(obj)) {
  console.log(value); // Danny 、 26 、 180 、 72
}

//遍歷物件：搭配 Object.entries() - ES8 新增
for (const [key, value] of Object.entries(obj)) {
  console.log(key); // name 、 age 、 height 、 weight
  console.log(value); // Danny 、 26 、 180 、 72
}
```

### **4.forEach**
作用對象：陣列
遍歷對象：鍵(key)、值(value)、作用對象(array)
```+
let arr = ['Eric', 'Allen', 'Owen'];

//遍歷陣列
arr.forEach((item, index, array) => {
  console.log(item); // Eric 、 Allen 、 Owen
  console.log(index); // 0 、 1 、 2
  console.log(array); // [ 'Eric', 'Allen', 'Owen' ]
});

let obj = {
  name: 'Danny',
  age: 26,
  height: 180,
  weight: 72,
};

//遍歷物件：搭配 Object.entries() - ES8 新增
Object.entries(obj).forEach((item) => {
  let [key, value] = item;
  console.log(key); // name 、 age 、 height 、 weight
  console.log(value); // Danny 、 26 、 180 、 72
});
```
## 使用情境:
### 是否遍歷自定義屬性和原型鏈上的屬性
> 遍歷陣列時，應該避免使用 for in，因為使用 for in 時，會連同自定義屬性和原型鏈上的屬性也一起遍歷(可以用obj.hasOwnProperty(property)事先檢查)
> 
> 💡`
<obj>.hasOwnProperty(<key>)`：只要是非繼承而來的屬性鍵都回傳 true，不論是否為 enumerable

* **不會忽略自定義屬性和原型鏈上的屬性：for in**

```+
let arr = ['red', 'blue', 'yellow'];

Array.prototype.inh = 'black';

for (let key in arr) {
  console.log(arr[key]); // red 、 blue 、 yellow 、 black
  if (arr.hasOwnProperty(key)) {
    console.log(arr[key]); // red 、 blue 、 yellow
  }
}
```
* **會忽略自定義屬性和原型鏈上的屬性：for、for of、forEach**
```+
let arr = ['red', 'blue', 'yellow'];

Array.prototype.inh = 'black';
arr.newPrototype = 'value';

/* --- for --- */
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]); // red 、 blue 、 yellow
}

/* --- for of --- */
for (const value of arr) {
  console.log(value); // red 、 blue 、 yellow
}

/* --- forEach --- */
arr.forEach((item) => {
  console.log(item); // red 、 blue 、 yellow
});
```
### 陣列的空元素
> for in、forEach 遇到空元素會直接跳過，for、for of 則不會

* **跳過空元素：for in、forEach**
```+
let arr = ['red', , 'blue'];
arr[4] = 'black';

/* --- for in --- */
for (const key in arr) {
  console.log(arr[key]); // red 、 blue 、 black
}

/* --- forEach --- */
arr.forEach((item) => {
  console.log(item); // red 、 blue 、 black
});
```
* **不會跳過空元素：for、for of**
```+
let arr = ['red', , 'blue'];
arr[4] = 'black';

/* --- for --- */
for (let i = 0; i < arr.length; i += 1) {
  console.log(arr[i]); // red 、 undefined 、 blue 、 undefined 、 black
}

/* --- for of --- */
for (const value of arr) {
  console.log(value); // red 、 undefined 、 blue 、 undefined 、 black
}
```
### this 的指向
> 基本上都是指向外部的 window 物件(forEach例外)
* **保留外部作用域：for、for in、for of**
```+
let arr = ['red'];

/* --- for --- */
for (let i = 0; i < arr.length; i += 1) {
  console.log(this); // window
}

/* --- for in --- */
for (const key in arr) {
  console.log(this); // window
}

/* --- for of --- */
for (const value of arr) {
  console.log(this); // window
}
```
* **指向特定的對象：forEach**
>forEach 所指向的 this 是根據thisArg 參數所提供，如果 thisArg 參數未定義或為 null，this 將根據設定模式指向對應的對象，嚴謹模式下為 undefined，非嚴謹模式下為 window。
* 非嚴謹模式
```+
let arr = ['red'];

/* --- 定義 thisArg 參數 --- */
arr.forEach(function(item) {
  console.log(this); // 30
}, 30);

/* --- 未定義 thisArg 參數 --- */
arr.forEach(function(item) {
  console.log(this); // window
});

/* --- 箭頭函式 --- */
arr.forEach((item) => {
  console.log(this); // window
});
```
   * 嚴謹模式
```+
'use strict'; 

let arr = ['red'];

/* --- 定義 thisArg 參數 --- */
arr.forEach(function(item) {
  console.log(this); // 30
}, 30);

/* --- 未定義 thisArg 參數 --- */
arr.forEach(function(item) {
  console.log(this); // undefined
});

/* --- 箭頭函式 --- */
arr.forEach((item) => {
  console.log(this); // window
});
```


### 中斷迴圈

* **中斷迴圈成功：for、for in、for of**
```+
let arr = ['red', 'blue', 'black'];

/* --- for --- */
for (let i = 0; i < arr.length; i += 1) {
  if (arr[i] === 'blue') {
    break;
  }
  console.log(i); // 0
}

/* --- for in --- */
for (const key in arr) {
  if (arr[key] === 'blue') {
    break;
  }
  console.log(key); // 0
}

/* --- for of --- */
for (const [index, value] of arr.entries()) {
  if (value === 'blue') {
    break;
  }
  console.log(index); // 0
}
```
* **中斷迴圈失敗：forEach**
> forEach 使用 break 會發生錯誤，使用 return 最多只能中斷當前遍歷項目，最後依然會遍歷後面的項目。
```+
/* --- break --- */
let arr = ['red', 'blue', 'black'];
arr.forEach((item, index) => {
  if (item === 'blue') {
    break;
  }
  console.log(index); // SyntaxError: Illegal break statement
});

/* --- return --- */
let arr = ['red', 'blue', 'black'];
arr.forEach((item, index) => {
  if (item === 'blue') {
    return;
  }
  console.log(index); // 0 、 2
});
```




### 表格:

|         | 作用對象       | 遍歷對象          | 是否遍歷自定義屬性和原型鏈上的屬性 | 是否跳過空元素 | this 的指向    | 中斷迴圈 | 版本    |
| ------- | -------------- | ----------------- | ---------------------------------- | -------------- | -------------- | -------- | --- |
| for     | 陣列           | 無                | 否                                 | 否             | 保留外部作用域 | 可以     |  ES1   |
| for in  | ~~陣列~~、物件 | key               | 是                                 | 是             | 保留外部作用域 | 可以     | ES1    |
| for of  | 陣列           | value             | 否                                 | 否             | 保留外部作用域 | 可以     |   ES6  |
| forEach | 陣列           | key、value、array | 否                                 | 是             | 指向特定的對象 | 不行     |   ES5  |

## 結論
1. for...in 輸出的是屬性名稱(key)，for...of 輸出的是值(value)。
1. 在遍歷物件屬性時，使用 for...in。
1. 在遍歷陣列時，使用 for...of，它比for迴圈簡潔，並且沒有for...in和forEach()那麼多奇怪的特例。
1. for 迴圈雖然冗長，但他比較彈性。

~~每篇結論推崇的不一樣，方法沒有特別的好壞，依據當下的需求跟習慣做使用。~~


## 補充:效能大比拚
測試方法:遍歷所有元素，存取裡的數值，然後看哪個方法速度最快。
測試結果:依序為快→慢
1.for
2.for...in
3.forEach
4.for...of
[使用兩種資料測試這四種方法的效能實驗](https://tigercosmos.xyz/post/2021/06/js/js-array-for-methods/)

## 參考資料:
[[筆記] 3 種 JavaScript 物件屬性的特性](https://medium.com/@liuderchi/%E7%AD%86%E8%A8%98-3-%E7%A8%AE-javascript-%E7%89%A9%E4%BB%B6%E5%B1%AC%E6%80%A7%E7%9A%84%E7%89%B9%E6%80%A7-3b982f4c5695)


[JavaScript 之旅 (4)：Object.keys() & Object.values() & Object.entries()](https://titangene.github.io/article/javascript-object-keys-values-entries.html)
[ES proposal: Object.entries() and Object.values()](https://2ality.com/2015/11/stage3-object-entries.html)


[JavaScript 容易混淆的遍歷方法](https://awdr74100.github.io/2019-11-28-javascript-traverse/)
[Looping over Arrays: for vs. for-in vs. .forEach() vs. for-of](https://2ality.com/2021/01/looping-over-arrays.html)
