Array.prototype.forEach()
forEach()方法对数组的每个元素执行一次提供的函数。
语法
arr.forEach(callback(currentValue [, index [, array]])[, thisArg]);
参数
callback- 为数组中每个元素执行的函数,该函数接收三个参数:
currentValue- 数组中正在处理的当前元素。
index可选- 数组中正在处理的当前元素的索引。
array可选forEach()方法正在操作的数组。
thisArg可选- 可选参数。当执行回调函数
callback时,用作this的值。
返回值
undefined。
描述
forEach()方法按升序为数组中含有效值的每一项执行一次callback函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。
可依次向callback函数传入三个参数:
- 数组当前项的值
- 数组当前项的索引
- 数组对象本身
如果thisArg参数有值,则每次callback函数被调用时,this都会指向thisArg参数。如果省略了thisArg参数,或者其值为null或undefined,this则指向全局对象。按照函数观察到this的常用规则,callback函数最终可观察到this值。
forEach()遍历的范围在第一次调用callback前就会确定。调用forEach后添加到数组中的项不会被callback访问到。如果已经存在的值被改变,则传递给callback的值是forEach()遍历到他们那一刻的值。已删除的项不会被遍历到。如果已访问的元素在迭代时被删除了(例如使用shift()),之后的元素将被跳过——参见下面的示例。
forEach()为每个数组元素执行一次callback函数;与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 callbackfn.',即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()处理。
示例
不对未初始化的值进行任何操作(稀疏数组)
如你所见,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
将 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);
});
打印出数组的内容
注意:为了在控制台中显示数组的内容,你可以使用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
使用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值。对象复制器函数
下面的代码会创建一个给定对象的副本。创建对象的副本有不同的方法,以下是只是一种方法,并解释了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 一模一样了
如果数组在迭代时被修改了,则其他元素会被跳过。
下面的例子会输出"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
扁平化数组
下面的示例仅用于学习目的。如果你想使用内置方法来扁平化数组,你可以考虑使用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]
针对 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
Polyfill
forEach()是在第五版本里被添加到 ECMA-262 标准的;这样它可能在标准的其他实现中不存在,你可以在你调用forEach()之前插入下面的代码,在本地不支持的情况下使用forEach()。该算法是 ECMA-262 第5版中指定的算法。它假定Function.prototype.call()。
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
// 1. Let O be the result of calling toObject() passing the
// |this| value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get() internal
// method of O with the argument "length".
// 3. Let len be toUint32(lenValue).
var len = O.length >>> 0;
// 4. If isCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let
// T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let k be 0
k = 0;
// 7. Repeat, while k < len
while (k < len) {
var kValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty
// internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal
// method of O with argument Pk.
kValue = O[k];
// ii. Call the Call internal method of callback with T as
// the this value and argument list containing kValue, k, and O.
callback.call(T, kValue, k, O);
}
// d. Increase k by 1.
k++;
}
// 8. return undefined
};
}
