# 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)