# 陣列與批次操作
>[竹白記事本](https://chupai.github.io/)
[JavaScript 竹白記事本 目錄](https://hackmd.io/@chupai/rkBjZjh7H)
###### tags: `JavaScript 竹白記事本`
## 陣列與批次操作
操作陣列可以使用 `for` 迴圈搭配 `length` 來迭代(遍歷)。
```javascript
const arr = [1, 2, 3, 4, 5];
for(let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// 1
// 2
// 3
// 4
// 5
```
但是使用 `for` 太繁瑣,需要索引值、結束條件、以及引值累加方法,而且不易閱讀。
因使我們可以使用陣列的方法來完成批次操作:
- `forEach()` 對每個元素執行動作
- `map()` 執行結果存到新陣列
- `filter()` 將符合條件的元素存到新陣列
- `find()` 找到第一個符合條件的元素
- `some()` 判斷有元素符合條件
- `every()` 判斷所有元素都符合條件
- `reduce()` 根據規則縮減陣列
`reduce()` 比較特別,其餘基本上差不多,只有些微的不同,以下將依序說明。
### 1. 遍歷陣列
#### 1.1 `forEach()`
[`forEach()`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) 方法會將陣列內的每個元素,皆傳入並執行給定的函式一次,不會產生新的陣列。
語法:
```javascript
arr.forEach(function callback(currentValue[, index[, array]]) {
//your iterator
}[, thisArg]);
```
- `callback` 函式共有三個參數:
- `currentValue`:陣列當前元素
- `index`:陣列當元素的索引值
- `array`:陣列本身
- `thisArg`:執行 `callback` 回呼函式的 `this`(即參考之 Object)值
`currentValue` 為必填參數,而 `index` 與 `array` 則選擇性。
試著改寫開頭的 `for` 迴圈,達到結果。
```javascript
const arr = [1, 2, 3, 4, 5];
arr.forEach(function(item) {
console.log(item);
})
// 1
// 2
// 3
// 4
// 5
```
當傳入參數 `thisArg`,`callback` 函式的 `this` 就會指向你所傳入的物件。
```javascript
const arr = [1, 2, 3];
const arr2 = ['一', '二', '三'];
arr.forEach(function(item, index) {
console.log(item + ':' + this[index]);
}, arr2);
// "1:一"
// "2:二"
// "3:三"
```
#### 1.2 `for...of`
`forEach()` 無法中止或跳出,除非拋出一個錯誤。
因此你有中斷需求,可以使用 ES6 新增的迭代迴圈 `for...of`。
```javascript
let arr = ['a', 'b', 'c', 'd', 'e'];
for (let element of arr) {
if (element === 'c') {
break;
}
console.log(element);
}
// "a"
// "b"
```
>關於 `for...of` 可以參考[《遍歷與迭代》](/SyUg2AQOH)。
### 2. 陣列轉換
#### 2.1 `map()`
[`map()`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/map) 方法會建立一個新的陣列,其內容為原陣列的每一個元素經由回呼函式運算後所回傳的結果之集合。
`map()` 語法參數基本上與 `forEach()` 相同,但 `map()` 多了一個 `return` 可以將會將處理結果放到新的陣列中,並回傳新的陣列。
`map()` 常用在物件的壓縮語轉換,只抽取或計算原來資料的必要資訊,來產生新的物件。
```javascript
const inventors = [
{ first: 'Albert', last: 'Einstein'},
{ first: 'Isaac', last: 'Newton'}
]
const name = inventors.map(function(item) {
return `${item.first} ${item.last}`;
});
console.log(name);
// ["Albert Einstein", "Isaac Newton"]
```
將 `firstname` 與 `lastname` 合成。
#### 2.2 `filter()`
[`filter()`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) 方法會建立一個經指定之函式運算後,由原陣列中通過該函式檢驗之元素所構成的新陣列。
語法參數基本上與 `forEach()` 相同,但 `filter()` 多了一個 `return` 是用來判斷條件。如果 `return` 值為 `true` 會將該元素放入新陣列。
通常用來篩選資料:
```javascript
const numbers = [33, 22, 66, 88, 10, 5, 6, 9];
const bigNum = numbers.filter(function(item) {
return item > 30;
});
console.log( bigNum ); // [33, 66, 88]
```
找出大於 `30` 的數值。
#### 2.3 `reduce()` 與 `reduceRight()`
[`reduce()`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) 方法將一個累加器及陣列中每項元素(由左至右)傳入回呼函式,將陣列化為單一值。
語法:
```javascript
arr.reduce(callback[accumulator, currentValue, currentIndex, array], initialValue)
```
`reduce()` 其實就是帶有暫存器的 `forEach()`。
- `accumulator`:用來累積回呼函式回傳值的累加器,若有提供的話,詳如下敘。累加器是上一次呼叫後,所回傳的累加數值。
- `initialValue`:於第一次呼叫 callback 時要傳入的累加器初始值。若沒有提供初始值,則原陣列的第一個元素將會被當作初始的累加器。假如於一個空陣列呼叫 `reduce()` 方法且沒有提供累加器初始值,將會發生錯誤。
- `return`:放入累加器中
假設要計算一個數值陣列的總合值為多少,使用 `for` 迴圈都會額外宣告一個 ` total ` 變數來當作累加器。
```javascript
const numbers = [1, 2, 3, 4, 5, 6];
let total = 0;
for(let i = 0; i < numbers.length; i++) {
total += numbers[i];
}
console.log( total );
// 21
```
使用 `reduce()`:
```javascript
const numbers = [1, 2, 3, 4, 5, 6];
const numTotal = numbers.reduce(function(total, item) {
return total + item
}, 0);
console.log( numTotal );
// 21
```
而 [`reduceRight()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight) 基本上與 `reduce()` 一樣,差異是從右到左進行累加。
我們用扁平化(flatten)一個元素為陣列的陣列來觀察差異:
```javascript
const arr = [[0, 1], [2, 3], [4, 5]];
function callback(a, b) {
return a.concat(b);
}
let result1 = arr.reduce(callback, []);
let result2 = arr.reduceRight(callback, []);
console.log(result1); // [0, 1, 2, 3, 4, 5]
console.log(result2); // [4, 5, 2, 3, 0, 1]
```
### 3. 陣列搜尋
#### 3.1 `some()`
[`some()`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/some) 方法會測試陣列中是否至少有一個元素通過由給定之函式所實作的測試。
語法參數基本上與 `forEach()` 相同,但 `some()` 多了一個 `return` 是用來判斷條件。如果至少 `return` 一次 `true` 則回傳 `true`。
`array` 陣列中使否有元素是奇數值:
```javascript
const array = [1, 2, 3, 4, 5];
const arraySome = array.some(function (even) {
return even % 2 === 0;
});
console.log( arraySome );
// true
```
#### 3.2 `find()` 與 `findIndex()`
要在一個陣列中搜尋某個值,一般來說會使用 `indexOf()`,它會回傳第一個符合的值的索引,沒找到回傳 `-1`。
```javascript
const arr = [1, 2, 3, 4, 5];
console.log(arr.indexOf(2)); // 1
```
但 `indexOf()` 是使用 `===` 來進行比較,因此如果傳入字串`'2'`,就會找不到。
```javascript
// 承接上段程式碼
console.log(arr.indexOf('2')); // -1
```
在 ES5 時,會使用 `some()` 來變通比對邏輯,但也只能判斷該陣列是否擁有該元素,無法實際獲取實際符合條件元素。
```javascript
// 承接上段程式碼
let result = arr.some(function(even) {
return even == '2';
})
console.log(result); // true
```
而到了 ES6 新增了 `find()`,解決了這個問題。
[`find()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/find) 方法會回傳第一個滿足所提供之測試函式的成員值。否則回傳 `undefined`。
與 `some()` 的差異在於,回傳第一個 `return` 值為 `true` 的元素。
找出第一個符合條件的元素:
```javascript
const inventory = [
{name: 'apples', quantity: 2},
{name: 'bananas', quantity: 0},
{name: 'cherries', quantity: 5}
];
const inventoryFind = inventory.find(function(item) {
return item.name === 'cherries';
});
console.log( inventoryFind );
// { name: 'cherries', quantity: 5 }
```
[`findIndex()`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) 也是 ES6 新增的方法,`findIndex()` 與 `find()` 差異只在於,其會回傳其索引,如果沒有符合的元素,將返回 `-1`。
```javascript
// 承接上段程式碼
const inventoryIndexFind = inventory.findIndex(function(item) {
return item.name === 'cherries';
});
console.log(inventoryIndexFind);
// 2
```
因此 `find()` 可以應用在更新資料,而 `findIndex()` 會用來刪除資料。
```javascript
const cart = [
{id: 1, count: 0},
{id: 2, count: 0},
{id: 3, count: 0},
{id: 4, count: 0},
];
```
找出 `id` 為 `3` 並將 `count` 更新成 `5`:
```javascript
let item = cart.find((item)=> item.id === 3);
item.count = 5;
console.log( cart );
// [
// {id: 1, count: 0},
// {id: 2, count: 0},
// {id: 3, count: 5},
// {id: 4, count: 0},
// ]
```
刪除 `id` 為 `3` 的元素:
```javascript
const cart = [
{id: 1, count: 0},
{id: 2, count: 0},
{id: 3, count: 0},
{id: 4, count: 0},
];
let index = cart.findIndex((item)=> item.id === 3);
cart.splice(index, 1);
console.log( cart );
// [
// {id: 1, count: 0},
// {id: 2, count: 0},
// {id: 4, count: 0},
// ]
```
#### 3.3 `every()`
[`every()`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/every) 方法會測試陣列中的所有元素是否都通過了由給定之函式所實作的測試。
與 `some()`的差異在於,`every()` 必須要全部的元素都符合條件才會回傳 `true`。
是否全部都是基數:
```javascript
const array = [1, 2, 3, 4, 5];
const arrayEvery = array.every(function (even) {
return even % 2 === 0;
});
console.log( arrayEvery );
// false
```