幫你精通JS: 函數(shù)式array.forEach的8個(gè)案例
JavaScript是當(dāng)今流行語(yǔ)言中對(duì)函數(shù)式編程支持最好的編程語(yǔ)言。我們繼續(xù)構(gòu)建函數(shù)式編程的基礎(chǔ),在前文中分解介紹了幫助我們組織思維的四種方法,分別為:
- array.reduce方法 幫你精通JS:神奇的array.reduce方法的10個(gè)案例
- array.map方法 幫你精通JS:神奇的array.map的6個(gè)案例
- array.flat方法,以及array.flatMap 幫你精通JS: array.flat與flatMap用法指南
以上四種方法的共同點(diǎn)都是對(duì)array作轉(zhuǎn)換和變形,而且都不需要陷入到瑣碎loop實(shí)現(xiàn)細(xì)節(jié)的 dirty details之中。
接下來(lái),我們將學(xué)習(xí)更加通用的函數(shù)式迭代方法 array.forEach()。
一句話概括區(qū)分 forEach 與 map 的區(qū)別,pure-function 就用 map,impure-function 則用 forEach。
array.forEach() 語(yǔ)法概述
forEach() 方法對(duì)數(shù)組的每個(gè)元素執(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ù)組中每個(gè)元素執(zhí)行的函數(shù),該函數(shù)接收一至三個(gè)參數(shù):
- currentValue 數(shù)組中正在處理的當(dāng)前元素。
- index 可選 數(shù)組中正在處理的當(dāng)前元素的索引。
- array 可選 forEach() 方法正在操作的數(shù)組。
thisArg 可選 可選參數(shù)。當(dāng)執(zhí)行回調(diào)函數(shù) callback 時(shí),用作 this 的值。
返回值
undefined。
array.forEach() 描述
forEach() 方法按升序?yàn)閿?shù)組中含有效值的每一項(xiàng)執(zhí)行一次 callback 函數(shù),那些已刪除或者未初始化的項(xiàng)將被跳過(guò)(例如在稀疏數(shù)組上)。
可依次向 callback 函數(shù)傳入三個(gè)參數(shù):
- 數(shù)組當(dāng)前項(xiàng)的值
- 數(shù)組當(dāng)前項(xiàng)的索引
- 數(shù)組對(duì)象本身
如果 thisArg 參數(shù)有值,則每次 callback 函數(shù)被調(diào)用時(shí),this 都會(huì)指向 thisArg 參數(shù)。如果省略了 thisArg 參數(shù),或者其值為 null 或 undefined,this 則指向全局對(duì)象。按照函數(shù)觀察到 this 的常用規(guī)則,callback 函數(shù)最終可觀察到 this 值。
forEach() 遍歷的范圍在第一次調(diào)用 callback 前就會(huì)確定。調(diào)用 forEach 后添加到數(shù)組中的項(xiàng)不會(huì)被 callback 訪問(wèn)到。如果已經(jīng)存在的值被改變,則傳遞給 callback 的值是 forEach() 遍歷到他們那一刻的值。已刪除的項(xiàng)不會(huì)被遍歷到。如果已訪問(wèn)的元素在迭代時(shí)被刪除了(例如使用 shift()),之后的元素將被跳過(guò)——參見(jiàn)下面的示例。
forEach() 為每個(gè)數(shù)組元素執(zhí)行一次 callback 函數(shù);與 map() 或者 reduce() 不同的是,它總是返回 undefined 值,并且不可鏈?zhǔn)秸{(diào)用。其典型用例是在一個(gè)調(diào)用鏈的最后執(zhí)行副作用(side effects,函數(shù)式編程上,指函數(shù)進(jìn)行 返回結(jié)果值 以外的操作)。
forEach() 被調(diào)用時(shí),不會(huì)改變?cè)瓟?shù)組,也就是調(diào)用它的數(shù)組(盡管 callback 函數(shù)在被調(diào)用時(shí)可能會(huì)改變?cè)瓟?shù)組)。(此處說(shuō)法可能不夠明確,具體可參考EMCA語(yǔ)言規(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 不會(huì)直接改變調(diào)用它的對(duì)象,但是那個(gè)對(duì)象可能會(huì)被 callback 函數(shù)改變。)
注意: 除了拋出異常以外,沒(méi)有辦法中止或跳出 forEach() 循環(huán)。如果你需要中止或跳出循環(huán),forEach() 方法不是應(yīng)當(dāng)使用的工具。
若你需要提前終止循環(huán),你可以使用:
- 一個(gè)簡(jiǎn)單的 for 循環(huán)
- for...of / for...in 循環(huán)
- Array.prototype.every()
- Array.prototype.some()
- Array.prototype.find()
- Array.prototype.findIndex()
這些數(shù)組方法則可以對(duì)數(shù)組元素判斷,以便確定是否需要繼續(xù)遍歷:
- every()
- some()
- find()
- findIndex()
只要條件允許,也可以使用 filter() 提前過(guò)濾出需要遍歷的部分,再用 forEach() 處理。
案例 01 不對(duì)未初始化的值進(jìn)行任何操作(稀疏數(shù)組)
如你所見(jiàn),3 和 7 之間空缺的數(shù)組單元未被 forEach() 調(diào)用 callback 函數(shù),或進(jìn)行任何其他操作。
- 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)轉(zhuǎ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ù)組的內(nèi)容
注意:為了在控制臺(tái)中顯示數(shù)組的內(nèi)容,你可以使用 console.table() 來(lái)展示經(jīng)過(guò)格式化的數(shù)組。下面的例子則是另一種使用 forEach() 的格式化的方法。
下面的代碼會(huì)為每一個(gè)數(shù)組元素輸出一行記錄:
- function logArrayElements(element, index, array) {
- console.log('a[' + index + '] = ' + element);
- }
- // 注意索引 2 被跳過(guò)了,因?yàn)樵跀?shù)組的這個(gè)位置沒(méi)有項(xiàng)
- [2, 5, , 9].forEach(logArrayElements);
- // logs:
- // a[0] = 2
- // a[1] = 5
- // a[3] = 9
案例 04 使用 thisArg
舉個(gè)勉強(qiáng)的例子,按照每個(gè)數(shù)組中的元素值,更新一個(gè)對(duì)象的屬性:
- 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)
因?yàn)?thisArg 參數(shù)(this)傳給了 forEach(),每次調(diào)用時(shí),它都被傳給 callback 函數(shù),作為它的 this 值。
注意:如果使用箭頭函數(shù)表達(dá)式來(lái)傳入函數(shù)參數(shù), thisArg 參數(shù)會(huì)被忽略,因?yàn)榧^函數(shù)在詞法上綁定了 this 值。
案例 05 對(duì)象復(fù)制器函數(shù)
下面的代碼會(huì)創(chuàng)建一個(gè)給定對(duì)象的副本。 創(chuàng)建對(duì)象的副本有不同的方法,以下是只是一種方法,并解釋了 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 看起來(lái)和 obj1 一模一樣了
案例 06 如果數(shù)組在迭代時(shí)被修改了,則其他元素會(huì)被跳過(guò)。
下面的例子會(huì)輸出 "one", "two", "four"。當(dāng)?shù)竭_(dá)包含值 "two" 的項(xiàng)時(shí),整個(gè)數(shù)組的第一個(gè)項(xiàng)被移除了,這導(dǎo)致所有剩下的項(xiàng)上移一個(gè)位置。因?yàn)樵?"four" 正位于在數(shù)組更前的位置,所以 "three" 會(huì)被跳過(guò)。 forEach() 不會(huì)在迭代之前創(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ù)組
下面的示例僅用于學(xué)習(xí)目的。如果你想使用內(nèi)置方法來(lái)扁平化數(shù)組,你可以考慮使用 Array.prototype.flat()(預(yù)計(jì)將成為 ES2019 的一部分,并且已在主要瀏覽器中實(shí)現(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 針對(duì) promise 或 async 函數(shù)的使用備注
如果使用 promise 或 async 函數(shù)作為 forEach() 等類似方法的 callback 參數(shù),最好對(duì)造成的執(zhí)行順序影響多加考慮,否則容易出現(xiàn)錯(cuò)誤。
- 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
【編輯推薦】






















