精通Javascript 函数式array.forEach的8个案例
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() 方法对数组的每个元素执行一次给定的函数。
-
const array1 = [‘a’, ‘b’, ‘c’];
-
array1.forEach(element => console.log(element));
-
// expected output: “a”
-
// expected output: “b”
-
// expected output: “c”
参数
callback
为数组中每个元素执行的函数,该函数接收一至三个参数:
-
currentValue 数组中正在处理的当前元素。
-
index 可选 数组中正在处理的当前元素的索引。
-
array 可选 forEach() 方法正在操作的数组。
thisArg 可选 可选参数。当执行回调函数 callback 时,用作 this 的值。
返回值
undefined。
array.forEach()描述
forEach() 方法按升序为数组中含有效值的每一项执行一次 callback函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。
可依次向 callback 函数传入三个参数:
-
数组当前项的值
-
数组当前项的索引
-
数组对象本身
如果 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 函数,或进行任何其他操作。
-
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 循环转换为 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 打印出数组的内容
注意:为了在控制台中显示数组的内容,你可以使用 console.table() 来展示经过格式化的数组。下面的例子则是另一种使用forEach() 的格式化的方法。
下面的代码会为每一个数组元素输出一行记录:
-
function logArrayElements(element, index, array) {
-
console.log('a[' + index + '] = ' + element);
-
}
-
// 注意索引 2 被跳过了,因为在数组的这个位置没有项
-
[2, 5, , 9].forEach(logArrayElements);
-
// logs:
-
// a[0] = 2
-
// a[1] = 5
-
// a[3] = 9
案例 04 使用 thisArg
举个勉强的例子,按照每个数组中的元素值,更新一个对象的属性:
-
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 参数(this)传给了 forEach(),每次调用时,它都被传给 callback 函数,作为它的 this 值。
注意:如果使用箭头函数表达式来传入函数参数, thisArg 参数会被忽略,因为箭头函数在词法上绑定了 this 值。
案例 05 对象复制器函数
下面的代码会创建一个给定对象的副本。 创建对象的副本有不同的方法,以下是只是一种方法,并解释了 Array.prototype.forEach() 是如何使用 ECMAScript 5 Object.* 元属性(meta property)函数工作的。
-
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); // 现在 obj2 看起来和 obj1 一模一样了
案例 06 如果数组在迭代时被修改了,则其他元素会被跳过。
下面的例子会输出 “one”, “two”, “four”。当到达包含值 “two” 的项时,整个数组的第一个项被移除了,这导致所有剩下的项上移一个位置。因为元素 “four” 正位于在数组更前的位置,所以"three" 会被跳过。 forEach() 不会在迭代之前创建数组的副本。
-
var words = [‘one’, ‘two’, ‘three’, ‘four’];
-
words.forEach(function(word) {
-
console.log(word);
-
if (word === 'two') {
-
words.shift();
-
}
-
});
-
// one
-
// two
-
// four
案例 07 扁平化数组
下面的示例仅用于学习目的。如果你想使用内置方法来扁平化数组,你可以考虑使用 Array.prototype.flat()(预计将成为 ES2019 的一部分,并且已在主要浏览器中实现)或参考其 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 函数的使用备注
如果使用 promise 或 async函数作为 forEach() 等类似方法的 callback 参数,最好对造成的执行顺序影响多加考虑,否则容易出现错误。
-
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
文章来源:网络 版权归原作者所有
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系小编,我们将立即处理
相关文章
- Javascript的DOM总结
- JavaScript中为何要使用prototype
- [Javascript] Create Your First Iterator in JavaScript
- [Javascript] Iterate Over Items with JavaScript's for-of Loop
- [Javascript] How to use JavaScript's String.replace
- [Javascript] An Introduction to JSPM (JavaScript Package Manager)
- JavaScript获取图片的原始尺寸
- [Javascript] Decorators in JavaScript
- [Javascript] Redirect the browser using JavaScript
- [Javascript] Creating an Immutable Object Graph with Immutable.js Map()
- JavaScript 本地对象、内置对象、宿主对象
- javascript案例14——判断奇偶数
- javascript案例9——我的生命、已活的时间、动态显示时间
- JavaScript学习总结(四)——this、原型链、javascript面向对象
- JavaScript学习总结(二)——延迟对象、跨域、模板引擎、弹出层、AJAX示例
- 2014年辛星解读Javascript之DOM之事件及其绑定
- 精通Javascript: 函数式array.forEach的8个案例