帮你精通JS: 函数式array.forEach的8个案例

开发 前端
JavaScript是当今流行语言中对函数式编程支持最好的编程语言。我们继续构建函数式编程的基础,接下来,我们将学习更加通用的函数式迭代方法 array.forEach()。

[[387134]]

 JavaScript是当今流行语言中对函数式编程支持最好的编程语言。我们继续构建函数式编程的基础,在前文中分解介绍了帮助我们组织思维的四种方法,分别为:

- array.reduce方法 帮你精通JS:神奇的array.reduce方法的10个案例

- array.map方法 帮你精通JS:神奇的array.map的6个案例

- array.flat方法,以及array.flatMap 帮你精通JS: array.flat与flatMap用法指南

以上四种方法的共同点都是对array作转换和变形,而且都不需要陷入到琐碎loop实现细节的 dirty details之中。

接下来,我们将学习更加通用的函数式迭代方法 array.forEach()。

一句话概括区分 forEach 与 map 的区别,pure-function 就用 map,impure-function 则用 forEach。

array.forEach() 语法概述

forEach() 方法对数组的每个元素执行一次给定的函数。

  1. const array1 = ['a''b''c']; 
  2.  
  3. array1.forEach(element => console.log(element)); 
  4.  
  5. // expected output"a" 
  6. // expected output"b" 
  7. // expected output"c" 

 参数

callback

为数组中每个元素执行的函数,该函数接收一至三个参数:

- currentValue 数组中正在处理的当前元素。

- index 可选 数组中正在处理的当前元素的索引。

- array 可选 forEach() 方法正在操作的数组。

thisArg 可选 可选参数。当执行回调函数 callback 时,用作 this 的值。

返回值

undefined。

array.forEach() 描述

forEach() 方法按升序为数组中含有效值的每一项执行一次 callback 函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。

可依次向 callback 函数传入三个参数:

  1. 数组当前项的值
  2. 数组当前项的索引
  3. 数组对象本身

如果 thisArg 参数有值,则每次 callback 函数被调用时,this 都会指向 thisArg 参数。如果省略了 thisArg 参数,或者其值为 null 或 undefined,this 则指向全局对象。按照函数观察到 this 的常用规则,callback 函数最终可观察到 this 值。

forEach() 遍历的范围在第一次调用 callback 前就会确定。调用 forEach 后添加到数组中的项不会被 callback 访问到。如果已经存在的值被改变,则传递给 callback 的值是 forEach() 遍历到他们那一刻的值。已删除的项不会被遍历到。如果已访问的元素在迭代时被删除了(例如使用 shift()),之后的元素将被跳过——参见下面的示例

forEach() 为每个数组元素执行一次 callback 函数;与 map() 或者 reduce() 不同的是,它总是返回 undefined 值,并且不可链式调用。其典型用例是在一个调用链的最后执行副作用(side effects,函数式编程上,指函数进行 返回结果值 以外的操作)。

forEach() 被调用时,不会改变原数组,也就是调用它的数组(尽管 callback 函数在被调用时可能会改变原数组)。(此处说法可能不够明确,具体可参考EMCA语言规范:'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 函数改变。)

注意: 除了抛出异常以外,没有办法中止或跳出 forEach() 循环。如果你需要中止或跳出循环,forEach() 方法不是应当使用的工具。

若你需要提前终止循环,你可以使用:

  • 一个简单的 for 循环
  • for...of / for...in 循环
  • Array.prototype.every()
  • Array.prototype.some()
  • Array.prototype.find()
  • Array.prototype.findIndex()

这些数组方法则可以对数组元素判断,以便确定是否需要继续遍历:

  • every()
  • some()
  • find()
  • findIndex()

只要条件允许,也可以使用 filter() 提前过滤出需要遍历的部分,再用 forEach() 处理。

案例 01 不对未初始化的值进行任何操作(稀疏数组)

如你所见,3 和 7 之间空缺的数组单元未被 forEach() 调用 callback 函数,或进行任何其他操作。

  1. const arraySparse = [1,3,,7]; 
  2. let numCallbackRuns = 0; 
  3.  
  4. arraySparse.forEach(function(element){ 
  5.   console.log(element); 
  6.   numCallbackRuns++; 
  7. }); 
  8.  
  9. console.log("numCallbackRuns: ", numCallbackRuns); 
  10.  
  11. // 1 
  12. // 3 
  13. // 7 
  14. // numCallbackRuns: 3 

 案例 02 将 for 循环转换为 forEach

  1. const items = ['item1''item2''item3']; 
  2. const copy = []; 
  3.  
  4. // before 
  5. for (let i=0; i<items.length; i++) { 
  6.   copy.push(items[i]); 
  7.  
  8. // after 
  9. items.forEach(function(item){ 
  10.   copy.push(item); 
  11. }); 

 案例 03 打印出数组的内容

注意:为了在控制台中显示数组的内容,你可以使用 console.table() 来展示经过格式化的数组。下面的例子则是另一种使用 forEach() 的格式化的方法。

下面的代码会为每一个数组元素输出一行记录:

  1. function logArrayElements(element, index, array) { 
  2.   console.log('a[' + index + '] = ' + element); 
  3.  
  4. // 注意索引 2 被跳过了,因为在数组的这个位置没有项 
  5. [2, 5, , 9].forEach(logArrayElements); 
  6. // logs: 
  7. // a[0] = 2 
  8. // a[1] = 5 
  9. // a[3] = 9 

 案例 04 使用 thisArg

举个勉强的例子,按照每个数组中的元素值,更新一个对象的属性:

  1. function Counter() { 
  2.   this.sum = 0; 
  3.   this.count = 0; 
  4. Counter.prototype.add = function(array) { 
  5.   array.forEach(function(entry) { 
  6.     this.sum += entry; 
  7.     ++this.count
  8.   }, this); 
  9.   // ^---- Note 
  10. }; 
  11.  
  12. const obj = new Counter(); 
  13. obj.add([2, 5, 9]); 
  14. obj.count
  15. // 3 === (1 + 1 + 1) 
  16. obj.sum
  17. // 16 === (2 + 5 + 9) 

 因为 thisArg 参数(this)传给了 forEach(),每次调用时,它都被传给 callback 函数,作为它的 this 值。

注意:如果使用箭头函数表达式来传入函数参数, thisArg 参数会被忽略,因为箭头函数在词法上绑定了 this 值。

案例 05 对象复制器函数

下面的代码会创建一个给定对象的副本。 创建对象的副本有不同的方法,以下是只是一种方法,并解释了 Array.prototype.forEach() 是如何使用 ECMAScript 5 Object.* 元属性(meta property)函数工作的。

  1. function copy(obj) { 
  2.   const copy = Object.create(Object.getPrototypeOf(obj)); 
  3.   const propNames = Object.getOwnPropertyNames(obj); 
  4.  
  5.   propNames.forEach(function(name) { 
  6.     const desc = Object.getOwnPropertyDescriptor(obj, name); 
  7.     Object.defineProperty(copy, namedesc); 
  8.   }); 
  9.  
  10.   return copy; 
  11.  
  12. const obj1 = { a: 1, b: 2 }; 
  13. const obj2 = copy(obj1); // 现在 obj2 看起来和 obj1 一模一样了 

 案例 06 如果数组在迭代时被修改了,则其他元素会被跳过。

下面的例子会输出 "one", "two", "four"。当到达包含值 "two" 的项时,整个数组的第一个项被移除了,这导致所有剩下的项上移一个位置。因为元素 "four" 正位于在数组更前的位置,所以 "three" 会被跳过。 forEach() 不会在迭代之前创建数组的副本。

  1. var words = ['one''two''three''four']; 
  2. words.forEach(function(word) { 
  3.   console.log(word); 
  4.   if (word === 'two') { 
  5.     words.shift(); 
  6.   } 
  7. }); 
  8. // one 
  9. // two 
  10. // four 

 案例 07 扁平化数组

下面的示例仅用于学习目的。如果你想使用内置方法来扁平化数组,你可以考虑使用 Array.prototype.flat()(预计将成为 ES2019 的一部分,并且已在主要浏览器中实现)或参考其 polyfill。

  1. /** 
  2.  * Flattens passed array in one dimensional array 
  3.  * 
  4.  * @params {array} arr 
  5.  * @returns {array} 
  6.  */ 
  7. function flatten(arr) { 
  8.   const result = []; 
  9.  
  10.   arr.forEach((i) => { 
  11.     if (Array.isArray(i)) 
  12.       result.push(...flatten(i)); 
  13.     else 
  14.       result.push(i); 
  15.   }) 
  16.  
  17.   return result; 
  18.  
  19. // Usage 
  20. const problem = [1, 2, 3, [4, 5, [6, 7], 8, 9]]; 
  21.  
  22. flatten(problem); // [1, 2, 3, 4, 5, 6, 7, 8, 9] 

 案例08 针对 promise 或 async 函数的使用备注

如果使用 promise 或 async 函数作为 forEach() 等类似方法的 callback 参数,最好对造成的执行顺序影响多加考虑,否则容易出现错误。

  1. let ratings = [5, 4, 5]; 
  2.  
  3. let sum = 0; 
  4.  
  5. let sumFunction = async function (a, b) { 
  6.     return a + b; 
  7.  
  8. ratings.forEach(async function(rating) { 
  9.     sum = await sumFunction(sum, rating); 
  10. }) 
  11.  
  12. console.log(sum); 
  13. // Expected output: 14 
  14. // Actual output: 0 

 【编辑推荐】

 

责任编辑:姜华 来源: 今日头条
相关推荐

2021-03-14 08:12:02

函数JavaScript语言

2021-03-17 06:03:41

函数式编程JavaScriptarray.filte

2021-03-05 07:45:59

JSreducemap

2021-04-08 09:14:24

js前端函数

2021-07-01 09:43:44

Python函数参数

2019-10-15 09:05:07

域插槽组件前端

2020-12-22 14:11:45

JS forEach()map()

2018-06-19 14:52:52

2012-05-09 09:49:57

移动支付

2024-01-16 12:19:08

MySQL重要机制高并发

2022-03-14 09:25:58

B 端UI信息密度

2011-03-30 13:03:14

数据库营销

2021-04-07 08:03:51

js举起Hoisting初始化

2016-11-28 08:56:15

透析大数据核心

2020-03-12 14:40:59

Python表格命令行

2011-03-31 11:15:52

网页设计Web

2017-11-10 09:30:43

Linux系统启动故障修复

2020-12-17 08:14:30

Linuxrsync备份

2021-08-30 20:12:11

MySQL事务隔离

2009-10-27 09:59:17

VB.NET动态代码
点赞
收藏

51CTO技术栈公众号