幫你精通JS: 函數(shù)式array.forEach的8個案例
JavaScript是當今流行語言中對函數(shù)式編程支持最好的編程語言。我們繼續(xù)構建函數(shù)式編程的基礎,在前文中分解介紹了幫助我們組織思維的四種方法,分別為:
- array.reduce方法 幫你精通JS:神奇的array.reduce方法的10個案例
- array.map方法 幫你精通JS:神奇的array.map的6個案例
- array.flat方法,以及array.flatMap 幫你精通JS: array.flat與flatMap用法指南
以上四種方法的共同點都是對array作轉換和變形,而且都不需要陷入到瑣碎loop實現(xiàn)細節(jié)的 dirty details之中。
接下來,我們將學習更加通用的函數(shù)式迭代方法 array.forEach()。
一句話概括區(qū)分 forEach 與 map 的區(qū)別,pure-function 就用 map,impure-function 則用 forEach。
array.forEach() 語法概述
forEach() 方法對數(shù)組的每個元素執(zhí)行一次給定的函數(shù)。
- const array1 = ['a', 'b', 'c'];
- array1.forEach(element => console.log(element));
- // expected output: "a"
- // expected output: "b"
- // expected output: "c"
參數(shù)
callback
為數(shù)組中每個元素執(zhí)行的函數(shù),該函數(shù)接收一至三個參數(shù):
- currentValue 數(shù)組中正在處理的當前元素。
- index 可選 數(shù)組中正在處理的當前元素的索引。
- array 可選 forEach() 方法正在操作的數(shù)組。
thisArg 可選 可選參數(shù)。當執(zhí)行回調函數(shù) callback 時,用作 this 的值。
返回值
undefined。
array.forEach() 描述
forEach() 方法按升序為數(shù)組中含有效值的每一項執(zhí)行一次 callback 函數(shù),那些已刪除或者未初始化的項將被跳過(例如在稀疏數(shù)組上)。
可依次向 callback 函數(shù)傳入三個參數(shù):
- 數(shù)組當前項的值
- 數(shù)組當前項的索引
- 數(shù)組對象本身
如果 thisArg 參數(shù)有值,則每次 callback 函數(shù)被調用時,this 都會指向 thisArg 參數(shù)。如果省略了 thisArg 參數(shù),或者其值為 null 或 undefined,this 則指向全局對象。按照函數(shù)觀察到 this 的常用規(guī)則,callback 函數(shù)最終可觀察到 this 值。
forEach() 遍歷的范圍在第一次調用 callback 前就會確定。調用 forEach 后添加到數(shù)組中的項不會被 callback 訪問到。如果已經(jīng)存在的值被改變,則傳遞給 callback 的值是 forEach() 遍歷到他們那一刻的值。已刪除的項不會被遍歷到。如果已訪問的元素在迭代時被刪除了(例如使用 shift()),之后的元素將被跳過——參見下面的示例。
forEach() 為每個數(shù)組元素執(zhí)行一次 callback 函數(shù);與 map() 或者 reduce() 不同的是,它總是返回 undefined 值,并且不可鏈式調用。其典型用例是在一個調用鏈的最后執(zhí)行副作用(side effects,函數(shù)式編程上,指函數(shù)進行 返回結果值 以外的操作)。
forEach() 被調用時,不會改變原數(shù)組,也就是調用它的數(shù)組(盡管 callback 函數(shù)在被調用時可能會改變原數(shù)組)。(此處說法可能不夠明確,具體可參考EMCA語言規(guī)范:'forEach does not directly mutate the object on which it is called but the object may be mutated by the calls to callback function.',即 forEach 不會直接改變調用它的對象,但是那個對象可能會被 callback 函數(shù)改變。)
注意: 除了拋出異常以外,沒有辦法中止或跳出 forEach() 循環(huán)。如果你需要中止或跳出循環(huán),forEach() 方法不是應當使用的工具。
若你需要提前終止循環(huán),你可以使用:
- 一個簡單的 for 循環(huán)
- for...of / for...in 循環(huán)
- Array.prototype.every()
- Array.prototype.some()
- Array.prototype.find()
- Array.prototype.findIndex()
這些數(shù)組方法則可以對數(shù)組元素判斷,以便確定是否需要繼續(xù)遍歷:
- every()
- some()
- find()
- findIndex()
只要條件允許,也可以使用 filter() 提前過濾出需要遍歷的部分,再用 forEach() 處理。
案例 01 不對未初始化的值進行任何操作(稀疏數(shù)組)
如你所見,3 和 7 之間空缺的數(shù)組單元未被 forEach() 調用 callback 函數(shù),或進行任何其他操作。
- const arraySparse = [1,3,,7];
- let numCallbackRuns = 0;
- arraySparse.forEach(function(element){
- console.log(element);
- numCallbackRuns++;
- });
- console.log("numCallbackRuns: ", numCallbackRuns);
- // 1
- // 3
- // 7
- // numCallbackRuns: 3
案例 02 將 for 循環(huán)轉換為 forEach
- const items = ['item1', 'item2', 'item3'];
- const copy = [];
- // before
- for (let i=0; i<items.length; i++) {
- copy.push(items[i]);
- }
- // after
- items.forEach(function(item){
- copy.push(item);
- });
案例 03 打印出數(shù)組的內容
注意:為了在控制臺中顯示數(shù)組的內容,你可以使用 console.table() 來展示經(jīng)過格式化的數(shù)組。下面的例子則是另一種使用 forEach() 的格式化的方法。
下面的代碼會為每一個數(shù)組元素輸出一行記錄:
- function logArrayElements(element, index, array) {
- console.log('a[' + index + '] = ' + element);
- }
- // 注意索引 2 被跳過了,因為在數(shù)組的這個位置沒有項
- [2, 5, , 9].forEach(logArrayElements);
- // logs:
- // a[0] = 2
- // a[1] = 5
- // a[3] = 9
案例 04 使用 thisArg
舉個勉強的例子,按照每個數(shù)組中的元素值,更新一個對象的屬性:
- function Counter() {
- this.sum = 0;
- this.count = 0;
- }
- Counter.prototype.add = function(array) {
- array.forEach(function(entry) {
- this.sum += entry;
- ++this.count;
- }, this);
- // ^---- Note
- };
- const obj = new Counter();
- obj.add([2, 5, 9]);
- obj.count;
- // 3 === (1 + 1 + 1)
- obj.sum;
- // 16 === (2 + 5 + 9)
因為 thisArg 參數(shù)(this)傳給了 forEach(),每次調用時,它都被傳給 callback 函數(shù),作為它的 this 值。
注意:如果使用箭頭函數(shù)表達式來傳入函數(shù)參數(shù), thisArg 參數(shù)會被忽略,因為箭頭函數(shù)在詞法上綁定了 this 值。
案例 05 對象復制器函數(shù)
下面的代碼會創(chuàng)建一個給定對象的副本。 創(chuàng)建對象的副本有不同的方法,以下是只是一種方法,并解釋了 Array.prototype.forEach() 是如何使用 ECMAScript 5 Object.* 元屬性(meta property)函數(shù)工作的。
- function copy(obj) {
- const copy = Object.create(Object.getPrototypeOf(obj));
- const propNames = Object.getOwnPropertyNames(obj);
- propNames.forEach(function(name) {
- const desc = Object.getOwnPropertyDescriptor(obj, name);
- Object.defineProperty(copy, name, desc);
- });
- return copy;
- }
- const obj1 = { a: 1, b: 2 };
- const obj2 = copy(obj1); // 現(xiàn)在 obj2 看起來和 obj1 一模一樣了
案例 06 如果數(shù)組在迭代時被修改了,則其他元素會被跳過。
下面的例子會輸出 "one", "two", "four"。當?shù)竭_包含值 "two" 的項時,整個數(shù)組的第一個項被移除了,這導致所有剩下的項上移一個位置。因為元素 "four" 正位于在數(shù)組更前的位置,所以 "three" 會被跳過。 forEach() 不會在迭代之前創(chuàng)建數(shù)組的副本。
- var words = ['one', 'two', 'three', 'four'];
- words.forEach(function(word) {
- console.log(word);
- if (word === 'two') {
- words.shift();
- }
- });
- // one
- // two
- // four
案例 07 扁平化數(shù)組
下面的示例僅用于學習目的。如果你想使用內置方法來扁平化數(shù)組,你可以考慮使用 Array.prototype.flat()(預計將成為 ES2019 的一部分,并且已在主要瀏覽器中實現(xiàn))或參考其 polyfill。
- /**
- * Flattens passed array in one dimensional array
- *
- * @params {array} arr
- * @returns {array}
- */
- function flatten(arr) {
- const result = [];
- arr.forEach((i) => {
- if (Array.isArray(i))
- result.push(...flatten(i));
- else
- result.push(i);
- })
- return result;
- }
- // Usage
- const problem = [1, 2, 3, [4, 5, [6, 7], 8, 9]];
- flatten(problem); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
案例08 針對 promise 或 async 函數(shù)的使用備注
如果使用 promise 或 async 函數(shù)作為 forEach() 等類似方法的 callback 參數(shù),最好對造成的執(zhí)行順序影響多加考慮,否則容易出現(xiàn)錯誤。
- let ratings = [5, 4, 5];
- let sum = 0;
- let sumFunction = async function (a, b) {
- return a + b;
- }
- ratings.forEach(async function(rating) {
- sum = await sumFunction(sum, rating);
- })
- console.log(sum);
- // Expected output: 14
- // Actual output: 0
【編輯推薦】