Array.prototype.forEach()
forEach()
方法对数组的每个元素执行一次给定的函数。
尝试一下
语法
// 箭头函数
forEach((element) => { /* … */ })
forEach((element, index) => { /* … */ })
forEach((element, index, array) => { /* … */ })
// 回调函数
forEach(callbackFn)
forEach(callbackFn, thisArg)
// 内联回调函数
forEach(function(element) { /* … */ })
forEach(function(element, index) { /* … */ })
forEach(function(element, index, array){ /* … */ })
forEach(function(element, index, array) { /* … */ }, thisArg)
参数
callbackFn
-
为数组中每个元素执行的函数。
函数调用时带有以下参数:
element
-
数组中正在处理的当前元素。
index
-
数组中正在处理的当前元素的索引。
array
-
forEach()
方法正在操作的数组。
thisArg
可选-
可选参数。当执行回调函数
callbackFn
时,用作this
的值。
返回值
undefined
。
描述
forEach()
方法按升序为数组中含有效值的每一项执行一次 callbackFn
函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上,见下方的示例)。
可依次向 callbackFn
函数传入三个参数:
- 数组当前项的值
- 数组当前项的索引
- 数组对象本身
如果 thisArg
参数有值,则每次 callbackFn
函数被调用时,this
都会指向 thisArg
参数。如果省略了 thisArg
参数,或者其值为 null
或 undefined
,this
则指向全局对象。按照函数观察到 this
的常用规则,callbackFn
函数最终可观察到 this
值。
forEach()
遍历的范围在第一次调用 callbackFn
前就会确定。调用 forEach
后添加到数组中的项不会被 callbackFn
访问到。如果已经存在的值被改变,则传递给 callbackFn
的值是 forEach()
遍历到他们那一刻的值。已删除的项不会被遍历到。如果已访问的元素在迭代时被删除了(例如使用 shift()
),之后的元素将被跳过——参见下面的示例。
forEach()
为每个数组元素执行一次 callbackFn
函数;与 map()
或者 reduce()
不同的是,它总是返回 undefined
值,并且不可链式调用。其典型用例是在一个调用链的最后执行副作用(side effects,函数式编程上,指函数进行 返回结果值 以外的操作)。
forEach()
被调用时,不会改变原数组,也就是调用它的数组(尽管 callbackFn
函数在被调用时可能会改变原数组)。(译注:此处说法可能不够明确,具体可参考 EMCA 语言规范:'forEach
does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn
.',即 forEach
不会直接改变调用它的对象,但是那个对象可能会被 callbackFn
函数改变。)
备注: 除了抛出异常以外,没有办法中止或跳出 forEach()
循环。如果你需要中止或跳出循环,forEach()
方法不是应当使用的工具。
若你需要提前终止循环,你可以使用:
- 一个简单的 for 循环
- for...of / for...in 循环
Array.prototype.every()
Array.prototype.some()
Array.prototype.find()
Array.prototype.findIndex()
这些数组方法则可以对数组元素判断,以便确定是否需要继续遍历:
译者注:只要条件允许,也可以使用 filter()
提前过滤出需要遍历的部分,再用 forEach()
处理。
示例
不对未初始化的值进行任何操作(稀疏数组)
const arraySparse = [1, 3, /* empty */, 7];
let numCallbackRuns = 0;
arraySparse.forEach((element) => {
console.log({ element });
numCallbackRuns++;
});
console.log({ numCallbackRuns });
// { element: 1 }
// { element: 3 }
// { element: 7 }
// { numCallbackRuns: 3 }
如你所见,3 到 7 之间的缺失值没有调用回调函数。
将 for 循环转换为 forEach
const items = ['item1', 'item2', 'item3'];
const copyItems = [];
// before
for (let i = 0; i < items.length; i++) {
copyItems.push(items[i]);
}
// after
items.forEach((item) => {
copyItems.push(item);
});
打印出数组的内容
备注: 为了在控制台中显示数组的内容,你可以使用 console.table()
来展示经过格式化的数组。下面的例子则是另一种使用 forEach()
的格式化的方法。
下面的代码会为每一个数组元素输出一行记录:
const 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
使用 thisArg
举个勉强的例子,按照每个数组中的元素值,更新一个对象的属性:
class Counter {
constructor() {
this.sum = 0;
this.count = 0;
}
add(array) {
// 只有函数表达式才有自己的 this 绑定
array.forEach(function countEntry(entry) {
this.sum += entry;
++this.count;
}, this);
}
}
const obj = new Counter();
obj.add([2, 5, 9]);
console.log(obj.count); // 3
console.log(obj.sum); // 16
因为 thisArg
参数(this
)传给了 forEach()
,每次调用时,它都被传给 callbackFn
函数,作为它的 this
值。
对象复制函数
下面的代码会创建一个给定对象的副本。创建对象的副本有不同的方法,以下是只是一种方法,并解释了 Array.prototype.forEach()
是如何使用 ECMAScript 5 Object.*
元属性(meta property)函数工作的。
const copy = (obj) => {
const copy = Object.create(Object.getPrototypeOf(obj));
const propNames = Object.getOwnPropertyNames(obj);
propNames.forEach((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 一模一样了
在迭代期间修改数组
下面的例子会输出 one
, two
, four
。
当到达包含值 two
的项时,整个数组的第一个项被移除了,这导致所有剩下的项上移一个位置。因为元素 four
正位于在数组更前的位置,所以 three
会被跳过。
forEach()
不会在迭代之前创建数组的副本。
const words = ['one', 'two', 'three', 'four'];
words.forEach((word) => {
console.log(word);
if (word === 'two') {
words.shift(); //'one' 将从数组中删除
}
}); // one // two // four
console.log(words); // ['two', 'three', 'four']
扁平化数组
下面的示例仅用于学习目的。如果你想使用内置方法来扁平化数组,你可以考虑使用 Array.prototype.flat()
。
const flatten = (arr) => {
const result = [];
arr.forEach((item) => {
if (Array.isArray(item)) {
result.push(...flatten(item));
} else {
result.push(item);
}
});
return result;
}
// 使用
const nested = [1, 2, 3, [4, 5, [6, 7], 8, 9]];
console.log(flatten(nested)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
规范
Specification |
---|
ECMAScript Language Specification # sec-array.prototype.foreach |
浏览器兼容性
BCD tables only load in the browser