概述
原文链接
事情的起源是这样的, 同事发给我两段代码, 如下:
var a = [1, 2, 3, 1, 2, 3];
a.forEach((item, index) => {
console.log(index, item);
if (item === 1) {
a.splice(index, 1);
}
});
// 输出
// 0 1
// 1 3
// 2 1
// 3 3
var a = [1, 2, 3, 1, 2, 3];
a.forEach((item, index) => {
console.log(index, item);
if (item === 1) {
a.push(1);
}
});
// 输出
// 0 1
// 1 2
// 2 3
// 3 1
// 4 2
// 5 3
复制代码
为什么第一个输出四次, 第二个不输出8次呢?
其实这样的事情在我们平常写代码的时候也经常发生, 如果这个改成 for 循环, 或许完全不一样. 那么 forEach
的 callback
到底执行了多少次呢?
这样的事情当然要看规范了, Array.prototype.forEach() 中文
forEach 方法按升序为数组中含有效值的每一项执行一次callback 函数,那些已删除(使用delete方法等情况)或者未初始化的项将被跳过(但不包括那些值为 undefined 的项)(例如在稀疏数组上)。
forEach 遍历的范围在第一次调用 callback 前就会确定。调用forEach 后添加到数组中的项不会被 callback 访问到。如果已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值。已删除的项不会被遍历到。如果已访问的元素在迭代时被删除了(例如使用 shift()) ,之后的元素将被跳过
这里面感觉最重要的是:
- forEach 遍历的范围在第一次调用 callback 前就会确定
- 如果已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值
看不懂? show me the code
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
// 如果 Array.prototype.forEach 没有定义的话
if (!Array.prototype.forEach) {
Array.prototype.forEach = function (callback/*, thisArg*/) {
// T 为 callback 的指向, 如果指定的话, 看 step-5
// k 为 循环的索引
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.
// @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object
// Object构造函数为给定值创建一个对象包装器。如果给定值是 null 或 undefined,将会创建并返回一个空对象,否则,将返回一个与给定值对应类型的对象。
// 当以非构造函数形式被调用时,Object 等同于 new Object()。
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).
// @see https://stackoverflow.com/questions/8286925/whats-array-length-0-used-for
// 保证 len 为一个小于 2^32 的整数
// 这里需要注意, step-7 的终止条件是 k < len;
// 所以, forEach 遍历的范围在第一次调用 callback 前就会确定
var len = O.length >>> 0;
// 4. If isCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
// 保证 callback 是个函数
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 = arguments[1];
}
// 6. Let k be 0.
k = 0;
// 7. Repeat while k < len.
while (k < len) {
// 第 k 项
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
// 保证 k 这个索引是 O 的属性
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, T 为 callback 绑定的 this, 参数分别是 item, index, 和 array 本身
callback.call(T, kValue, k, O);
}
// d. Increase k by 1.
k++;
}
// 8. return undefined.
};
}
复制代码
刚刚说的两条分别对应 step-3
// 3. Let len be toUint32(lenValue).
var len = O.length >>> 0;
复制代码
和 step-7-c
if (k in O)
复制代码
回到第一题
var a = [1, 2, 3, 1, 2, 3];
a.forEach((item, index) => {
console.log(index, item);
if (item === 1) {
a.splice(index, 1);
}
});
复制代码
1. a = [1,2,3,1,2,3]; len = 6
2. k = 0; console.log(0, 1);
3. splice(0, 1) ---> a = [2,3,1,2,3]
4. k = 1; console.log(1, 3);
5. k = 2; console.log(2, 1);
6. splice(2, 1) ---> a = [2,3,2,3];
7. k = 3; console.log(3, 3);
8. k = 4; k not in a;
9. k = 5; k not in a;
复制代码
第二题比较简单, 新添加的两个 1 都不会遍历
所以两种情况的 while 循环都是 6 次
但是第一种由于 '4'
'5'
都不在 array 里面, 所以 callback 只执行了 4 次
第二种情况 callback 执行了 6 次
好啦, 你听明白了嘛~
参考资料
- Array.prototype.forEach()
- Array.prototype.forEach() - 中文
最后
以上就是大胆冰淇淋为你收集整理的Array.prototype.forEach(callback) 的 callback 到底执行了几次?的全部内容,希望文章能够帮你解决Array.prototype.forEach(callback) 的 callback 到底执行了几次?所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复