JavaScript很简单?那你理解forEach对吗?
web前端开发
共 3913字,需浏览 8分钟
·
2020-12-18 00:15
来源 | https://king-hcj.github.io/2020/10/03/you-dont-know-foreach/
Array.prototype.forEach
callback:为数组中每个元素执行的函数,该函数接收一至三个参数: currentValue:数组中正在处理的当前元素。 index 可选,数组中正在处理的当前元素的索引。 array 可选,forEach() 方法正在操作的数组。 thisArg 可选参数。当执行回调函数 callback 时,用作 this 的值。 返回值:undefined
const array1 = ['a', 'b', 'c'];
array1.forEach((element) => console.log(element)); // 输出:a,b,c
这个 forEach 的实现真的对吗?
Array.prototype.forEachCustom = function (fn, context) {
context = context || arguments[1];
if (typeof fn !== 'function') {
throw new TypeError(fn + 'is not a function');
}
for (let i = 0; i < this.length; i++) {
fn.call(context, this[i], i, this);
}
};
看起来没有问题,我们测试一下:
const items = ['item1', 'item2', 'item3'];
items.forEach((item) => {
console.log(item); // 依次打印:item1,item2,item3
});
items.forEachCustom((item) => {
console.log(item); // 依次打印:item1,item2,item3
});
好的,似乎没有问题,一切貌似都很完美。我们再测试下下面几个示例:
// 示例1
const items = ['', 'item2', 'item3', , undefined, null, 0];
items.forEach((item) => {
console.log(item); // 依次打印:'',item2,item3,undefined,null,0
});
items.forEachCustom((item) => {
console.log(item); // 依次打印:'',item2,item3,undefined,undefined,null,0
});
// 示例2
let arr = new Array(8);
arr.forEach((item) => {
console.log(item); // 无打印输出
});
arr[1] = 9;
arr[5] = 3;
arr.forEach((item) => {
console.log(item); // 打印输出:9 3
});
arr.forEachCustom((item) => {
console.log(item); // 打印输出:daundefined 9 undefined*3 3 undefined*2
});
貌似发生了什么可怕的事儿,同样的数组经过 forEachCustom 和 forEach 调用,在打印出的值和值的数量上均有差别。看来我以为的并不真的就是我以为的。
追本溯源
怎么办呢?咱不妨去看看 ECMA 文档,看看 forEach 是怎么实现的:
Array.prototype.forEachCustom = function (fn, context) {
context = context || arguments[1];
if (typeof fn !== 'function') {
throw new TypeError(fn + 'is not a function');
}
let len = this.length;
let k = 0;
while (k < len) {
// ECMA文档使用的是HasProperty,在此,使用in应该比hasOwnProperty更确切
// if (this.hasOwnProperty(k)) {
// fn.call(context, this[k], k, this);
// };
if (k in this) {
fn.call(context, this[k], k, this);
};
k++;
}
};
// 示例3
var words = ['one', 'two', 'three', 'four'];
words.forEach(function (word) {
console.log(word); // one,two,four(在迭代过程中删除元素,导致three被跳过,因为three的下标已经变成1,而下标为1的已经被遍历了过)
if (word === 'two') {
words.shift();
}
});
words = ['one', 'two', 'three', 'four']; // 重新初始化数组进行forEachCustom测试
words.forEachCustom(function (word) {
console.log(word); // one,two,four
if (word === 'two') {
words.shift();
}
});
// 示例4
var arr = [1, 2, 3];
arr.forEach((item) => {
if (item == 2) {
arr.push(4);
arr.push(5);
}
console.log(item); // 1,2,3(迭代过程中在末尾增加元素,并不会使迭代次数增加)
});
arr = [1, 2, 3];
arr.forEachCustom((item) => {
if (item == 2) {
arr.push(4);
arr.push(5);
}
console.log(item); // 1,2,3
});
番外篇
除了抛出异常以外,没有办法中止或跳出 forEach() 循环。如果你需要中止或跳出循环,forEach() 方法不是应当使用的工具。若你需要提前终止循环,你可以使用:
一个简单的 for 循环 for…of / for…in 循环 Array.prototype.every() Array.prototype.some() Array.prototype.find() Array.prototype.findIndex()
every() some() find() findIndex()
总结
forEach 不对未初始化的值进行任何操作(稀疏数组); 在迭代前,循环的次数就已经定了,且执行了循环,不代表就一定会执行回调函数; 除了抛出异常以外,没有办法中止或跳出 forEach() 循环。
ecma-262/6.0 ecma-262/11.0
本文完~
评论