我是靠谱客的博主 大胆冰淇淋,最近开发中收集的这篇文章主要介绍Array.prototype.forEach(callback) 的 callback 到底执行了几次?,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

原文链接

事情的起源是这样的, 同事发给我两段代码, 如下:

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 循环, 或许完全不一样. 那么 forEachcallback 到底执行了多少次呢?

这样的事情当然要看规范了, 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 到底执行了几次?所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(50)

评论列表共有 0 条评论

立即
投稿
返回
顶部