數組的遍歷及歸併方法簡要總結 reduce() reduceRight() forEach() map() filter() every() some()等

數組是出現頻率最高的數據結構之一, 而遍歷是對數組作的最多的操做. 能夠用來對進行遍歷的函數有不少, 並且每一個函數都有各自的適用情景, 要作根據不一樣的需求中選擇最合理的函數, 必須先對這些函數各自的特色有所瞭解.數組

本文涉及的數組遍歷相關的函數

首先將文中所討論的全部方法列出, 以便有總體印象. 這些方法能夠分爲兩種, 分別是:數據結構

  • 數組的遍歷方法, 共 7 個:函數

    1. forEach()
    2. map()
    3. every()
    4. some()
    5. filter()
    6. find()
    7. findIndex()
  • 數組的歸併方法, 共 2 個:ui

    1. reduce()
    2. reduceRight()

下面依次討論並比較所列出方法各自的功能和特色.this

數組的遍歷方法

上面提到的 7 種數組的遍歷方法都接受 2 個參數 (callback, thisArg):spa

  1. 第一個是回調函數 callback , 會對數組的每個元素都執行這個函數. 回調函數接受三個參數: 正在處理的當前元素( currentElement )、正在處理的當前元素的索引( currentIndex )、當前正在被操做的數組( currentArray ).code

  2. 第二個參數是 thisArg, 是給 callback 函數指定的 this. 這個參數是可選的, 若是不指定, 則默認是 undefined.索引

下面具體討論每一個方法的特色.ip

forEach() 函數

這個函數是比較常見的, 它不返回任何值, 只對數組的每一個元素都執行回調函數. 就像上面說的, 回調函數的參數是數組裏的元素、索引和這個數組自己, 例如訪問數組的每個元素和索引:字符串

let array = ['a', 'b', 'c'];
array.forEach(function(curElement, curIndex, curArray){
    console.log('索引爲 ' + curIndex + ' 的元素值是 ' + curElement);
});

// 索引爲 0 的元素值是 a
// 索引爲 1 的元素值是 b
// 索引爲 2 的元素值是 c
複製代碼

幾個須要注意的狀況

  • 當數組的某個位置沒有值時, 則這個位置會被跳過. 但值是 undefinednull 的位置不會被當作空, 例如:
// 設置數組的第二個元素爲空, 第 三、 4 個元素是 null 和 undefined
let array = ['a', , null, undefined, 'c'];  

array.forEach(function(curElement, curIndex, curArray){ // forEach 遍歷這個數組
    console.log('索引爲 ' + curIndex + ' 的元素值是 ' + curElement);
});

/* 輸出的結果中代表跳過了 1 位置的元素, 可是並無 跳過值爲 undefined 和 null 的位置 索引爲 0 的元素值是 a 索引爲 2 的元素值是 null 索引爲 3 的元素值是 undefined 索引爲 4 的元素值是 c */
複製代碼
  • 遍歷過程當中沒有辦法停止或者跳出 forEach() 循環, 除了經過拋出一個異常來退出。 若是確實須要這樣作, 那麼使用 forEach() 並非合適的方法, 其餘幾種遍歷方法中有很是適合這個需求的.

map() 函數

map() 方法對數組的每一個元素都運行回調函數, 而後用回調函數的返回值組成一個新數組返回. 也就是說 map() 方法的回調函數須要明確指定返回值是什麼. 這就和上面的 forEach() 方法不一樣了: forEach() 的回調函數並不顯式的返回任何值.

經過上面的描述能夠知道 map() 方法通常用於對數組的每一個元素進行二次加工(映射), 可是又不但願去改變原來數組, 因此返回一個新的數組.

例如, 想獲得數組中的元素都乘上2以後的結果, 使用 map 方法就很合適了:

let oldArr = [1, 2, 3];
let newArr = oldArr.map(function(curElement){
    return curElement * 2;
});

console.log(newArr); 
// Array(3) [2, 4, 6], 能夠看到 newArr 中的元素都是 oldArr 的元素乘上2以後的結果

console.log(oldArr);
// Array(3) [1, 2, 3] , 原來的數組並無被修改
複製代碼

或者求每一個元素值的平方根

let numbers = [1, 4, 9];
let roots = numbers.map(Math.sqrt); // 將自帶的函數做爲回調函數傳給 map 
// roots的值爲[1, 2, 3], numbers的值仍爲[1, 4, 9]
複製代碼

注意: 回調函數的三個參數(curElement, curIndex, curArray)中只有第一個參數 curElement 是必需要指定的, 其餘兩個參數能夠不指定, 可是實際上依然會被傳入回調函數中. 這聽起來很繞, 能夠經過下面的一個在網上流傳很廣的題目來了解這個知識點.

一道關於 map 函數的著名題目

常常在網上見到這個題目露面, 並且答案看起來很詭異, 不過經過這個題目能夠了解到 map 函數的一個知識點.

題目是: 下面這段代碼會輸出什麼

console.log(["1", "2", "3"].map(parseInt));
複製代碼

答案是並非想象中的 [1, 2, 3] , 而是 [1, NaN, NaN]. 下面來分析答案爲何是這個.

先明確代碼["1", "2", "3"].map(parseInt)中的主角都有哪些, 從後向前看:

  1. parseInt 函數: 這個函數的做用是解析出來一個字符串中的整數, 它接受兩個參數: 要解析的字符串和基數, 這個基數表示想要以哪一個進制來解析這個字符串. 基數若是不指定, 則默認爲 10, 即按照 十進制 來解析字符串中的整數. 在上面的代碼中 parseInt 做爲 map 的回調函數.

  2. map() 函數, 它會向回調函數傳入三個參數, 即便在咱們只指定一個參數名的時候, 另外的兩個參數也會隱式的向回調函數中傳入.

  3. ["1", "2", "3"]數組, map 函數的調用者.

在明確了三個主角以後, 下面要作的就是理清主角之間的關係: 數組 --調用--> map函數, parseInt 做爲 map 的回調函數被調用.

關鍵點來了, map 會向回調函數 parseInt 傳入三個參數: 當前元素,當前索引和當前數組. 那麼這時的 parseInt 函數就能夠看作:

parseInt(curElement, curIndex, curArray)
複製代碼

然而 parseInt 最多接受兩個參數, 那麼第三個參數(當前數組)就被忽略了 . 因而 parseInt 最終變成 parseInt(curElement, curIndex) 這種調用形式.

這樣整個題目就變成了:

console.log(["1", "2", "3"].map(parseInt(curElement, curIndex)));
複製代碼

每一個元素的 curIndex 做爲基數. 將循環拆開, 能夠依次看出 parseInt 對每一個元素進行的操做:

[ parseInt('1', 0), parseInt('2', 1), parseInt('3', 2)];  
// [1 NaN NaN]
複製代碼

經過以上就能夠理解這個題目的答案是怎麼來的了. 固然還要了解關於 parseInt 函數的機制, 這是另一個話題, 再討論起來就偏題了. 具體參見 MDN

顧名思義的 every()some() 函數

這兩個函數都用回調函數檢測數組中的元素是否知足給定的條件, 返回一個布爾值.

對於 every() 來講, 只有每一個元素都知足回調函數中定義的條件, 纔會返回 true, 不然返回 false.

而對於 some() 來講, 只要有一個元素知足條件, 函數就會返回 true, 不然返回 false.

例如, 檢測一個數組中是否全部數值都 > 10:

let arr = [20, 30, 40];  // 定義一個元素都 > 10 的數組

let boolValue = arr.every(function(curElement, curIndex, curArray){
    return curElement > 10;  // 回調函數返回當前元素是否知足 > 10 的布爾值
});
console.log(boolValue);  // true
複製代碼

注意: 空數組調用every這個函數會返回 true.

再例如, 檢測一個數組中是否是存在 > 10 的元素:

let arr = [2,3,40];

let boolValue = arr.some(function(curElement, curIndex, curArray){
    return curElement > 10; // 回調函數返回當前元素是否知足 > 10 的布爾值
});

console.log(boolValue);  // true
複製代碼

注意1: 空數組調用some這個函數會返回 false.

注意2: some 會從前日後遍歷數組, 一旦找到一個知足條件的元素, 就會返回 true, 中止遍歷, 並不會再遍歷後面的數組. 驗證代碼以下:

[2, 3, 40, 50].some(function(curElement, curIndex, curArray){
    console.log(curIndex); // 當前元素的 位置
    return curElement > 2; // 返回當前元素是否知足 > 10 這個條件
});

// 輸出: 0 1 , 能夠看到只訪問到了 1 位置, 因爲 1 位置的元素 3 > 2, 因此就不向後繼續遍歷了
複製代碼

從上面的輸出中能夠知道 some 在遍歷到第 1 個位置時找到了知足條件的數組項, 函數就中止了執行, 再也不遍歷以後的數組.

一樣顧名思義的 filter() 函數

filter 有過濾的意思, 顧名思義, 這個函數會返回知足回調函數的全部元素所組成的新數組. 若是全部元素都不知足, 則返回一個 空數組.

例如, 返回數組中全部 > 10 的元素:

let arr = [2, 3, 40]; // 建立只有一個元素 > 10 的數組

let newArr = arr.filter(function(curElement, curIndex, curArray){
    return curElement > 10; // 回調函數返回當前元素是否知足 > 10 的布爾值
});

console.log(newArr); // [40], 只有 40 > 10, 則返回的數組中只包含 40 
複製代碼

數組的查找方法 find()findIndex() 函數

find() 函數返回數組中知足回調函數條件的第一個元素. 若是沒有元素知足條件就返回 undefined.

findIndex() 函數返回數組中知足回調函數條件的第一個元素的索引, 若是沒有元素知足條件就返回 -1.

例如, 想找到數組中第一個 > 10 的元素用 find, 想找到這個元素的位置用 findIndex:

let arr = [2, 3, 40, 50]; // 數組中第一個 > 10 的元素是 40, 索引是 2

let item = arr.find(function(curElement, curIndex, curArray){
    return curElement > 10; // 返回當前元素是否知足 > 10 這個條件
});

let index = arr.findIndex(function(curElement, curIndex, curArray){
    return curElement > 10; // 返回當前元素是否知足 > 10 這個條件
});

console.log(item); // 40
console.log(index); // 2
複製代碼

必需要知道的7個函數的共同點

這 7 種方法遍歷數組的過程是按索引依次訪問數組每一項的過程. 在開始遍歷以前會事先肯定數組的長度 len, 從 0 位置依次忠實的訪問到 len - 1 這個位置, 無論數組怎麼變化, len 的值就像被 const 定義的同樣---直到遍歷完成以前永遠不變. 然而在遍歷的過程當中數組自己可能會發生變化, 例如長度變化和元素變化, 可分紅如下 3 種狀況: 1. 數組的長度不變, 可是其中的元素髮生了變化 不管每一個元素的值怎麼變化, 始終按當前的值爲準

2. 數組元素增長的狀況, 例如使用 `push` 方法往數組裏塞進一個新的元素. 

假設遍歷以前數組的長度值是 len, 則只會訪問到 len - 1 位置, 不管數組增長了多少元素, len - 1 以後的位置都不會被訪問到. 以 `forEach` 函數爲例來講明, 例如:
```js
let array = ['小x' , '小明', '小紅'];
array.forEach(function(curElement, curIndex, curArray){
    console.log(curIndex + ' 位置是 ' + curElement); 

    // 遍歷到小明的時候向數組裏添加新元素
    if(curElement === '小明'){
        array.push('小新'); 
        array.push('小新新');
    }
});

/* 查看輸出的結果 並無遍歷到新加入的元素

0 位置是 小x
1 位置是 小明
2 位置是 小紅
*/ 

// 將數組打印出來觀察, 發現其中確實增長了新的元素
console.log(array);
// Array(5) ["小x", "小明", "小紅", "小新", "小新新"]
```
因此, 不要嘗試在遍歷的過程當中向數組**後面**添加元素並期待能訪問到它們.

3. 數組元素減小
會遍歷到數組元素減小後的數組的最後一個位置. 例如數組原本有 n 個元素, 遍歷過程當中元素個數變成了 n - 1, 則只會遍歷到 n - 2 這個位置了.
複製代碼

這 7 個函數接受的參數都是相同的: 回調函數和用來指定this值的參數. 可是他們的回調函數接受的參數數量可能會不一樣, 好比 map 的回調函數能夠不指定第2、三個參數.

簡單總結上述 7 個方法

  1. forEach: 無返回值. 對每一個元素執行回調函數.
  2. map: 返回一個數組. 每一個元素執行回調函數, 返回全部由回調函數結果組成的數組.
  3. every: 返回一個布爾值. 用回調函數提供的條件判斷每一個元素, 若是全部的元素都知足, 則返回 true, 不然 false.
  4. some: 返回一個布爾值. 從頭至尾用回調函數提供的條件判斷每一個元素, 若是有元素知足條件, 就中止循環, 返回 true, 若是數組中的元素都不知足條件, 返回 false.
  5. filter: 返回一個數組. 數組中是知足條件的元素.
  6. find: 返回一個值. 遍歷數組, 返回第一個知足條件的元素值. 若是都不知足, 則返回 undefined.
  7. findIndex: 和 find 函數類似, 不過返回的是知足條件元素的索引. 不然返回 -1.

數組的歸併方法, reduce()reduceRight()

這兩個函數都會遍歷數組並返回由回調函數計算出來的值, 不一樣點僅在於這兩個函數遍歷數組的方向不一樣, 前者從數組的第一項遍歷到最後一項, 後者從最後一項遍歷到第一項. 因此, 這裏只討論 reduce.

reduce()

reduce() 函數能夠指定兩個參數, 回調函數 callback 和一個做爲初始值的 initValue.

回調函數接受四個參數, 分別是:

  1. 當前的累加值: count
  2. 當前元素: curElement
  3. 當前索引: curIndex
  4. 當前的數組: curArray

因此這個函數的完整語法能夠寫做以下:

reduce(function(count, curElement, curIndex, curArray){}, initValue);
複製代碼

其中回調函數的第一個參數( count )的值是在訪問上一個元素時的回調函數的返回值. 換句話說, 當前的回調函數的返回值會被賦下一次執行的回調函數的參數 count.

這裏可能有 2 個疑惑:

  1. 在遍歷開始以前, count 的值是什麼?
  2. 初始值 initValue 是用來作什麼的? 下面的內容會討論這兩個問題.

第二個參數 initValue 對回調函數的影響

reduce 函數中的第二個參數 initValue 是能夠省略的. 但省略與否會影響回調函數中前三個參數的初始值. 具體要結合實驗來講明以下:

  • 不指定參數 initValue 的值時, 回調函數的參數 curIndex = 1, curElement 值爲 array[1], count 爲 arr[0]:
let arr = [2, 3]; // 建立有兩個元素的數組以便觀察

arr.reduce(function(count, curElement, curIndex, curArr){
    console.log(count, curElement, curIndex);  // 2 3 1
});
複製代碼

能夠看到 count === arr[0], curIndex === 1, curElement === arr[1]. 即從數組的第二個元素開始向後遍歷.

  • 指定參數 initValue 的值時, 回調函數的參數 curIndex = 0, curElement 值爲a[0], count 值爲 initValue:
let arr = [2]; // 建立有一個元素的數組以便觀察

arr.reduce(function(count, curElement, curIndex, curArr){
    console.log(count, curElement, curIndex);  // 100 2 0
}, 100); // 指定 initValue 爲 100
複製代碼

能夠看到 count === initValue, curIndex === 0, curElement === arr[0]. 這時纔是從數組的第一個元素開始向後遍歷.

reduce 函數使用案例

上面的內容提到「 回調函數的第一個參數( count )的值是在訪問上一個元素時的回調函數的返回值. 換句話說, 當前的回調函數的返回值會被賦下一次執行的回調函數的參數 count 」. 這個特色正好能夠用來求一個數組全部元素的和, 能夠把 count 看作以前全部元素的總和, 把當前的值和 count 加起來就是數組到如今位置的和.

let arr = [1, 2, 3, 4, 5]; 
let sum = arr.reduce(function(count, curElement, curIndex, curArr){
    console.log('數組前 ' + curIndex + ' 個元素的和是: ' + count );
    console.log('當前是數組第 ' + (curIndex + 1) + ' 個元素, 值是: ' + curElement);
    console.log('加上當前元素後的值是: ' + (count + curElement));
    console.log('-------------------------------');

    return count + curElement; // 前面全部的和 + 當前的元素值
}); 

console.log(sum);

/* 輸出 數組前 1 個元素的和是: 1 當前是數組第 2 個元素, 值是: 2 加上當前元素後的值是: 3 ------------------------------- 數組前 2 個元素的和是: 3 當前是數組第 3 個元素, 值是: 3 加上當前元素後的值是: 6 ------------------------------- 數組前 3 個元素的和是: 6 當前是數組第 4 個元素, 值是: 4 加上當前元素後的值是: 10 ------------------------------- 數組前 4 個元素的和是: 10 當前是數組第 5 個元素, 值是: 5 加上當前元素後的值是: 15 ------------------------------- 數組的和是: 15 */
複製代碼

能夠看到回調函數的返回值被賦給了 count 以供下次使用, 最後一個 count 就是整個數組的和.

#完.

相關文章
相關標籤/搜索