Array.prototype.forEach(callback) 的 callback 到底执行了几回?

原文连接javascript

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

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次呢?git

其实这样的事情在咱们日常写代码的时候也常常发生, 若是这个改为 for 循环, 或许彻底不同. 那么 forEachcallback 到底执行了多少次呢?github

这样的事情固然要看规范了, Array.prototype.forEach() 中文数组

forEach 方法按升序为数组中含有效值的每一项执行一次callback 函数,那些已删除(使用delete方法等状况)或者未初始化的项将被跳过(但不包括那些值为 undefined 的项)(例如在稀疏数组上)。bash

forEach 遍历的范围在第一次调用 callback 前就会肯定。调用forEach 后添加到数组中的项不会被 callback 访问到。若是已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值。已删除的项不会被遍历到。若是已访问的元素在迭代时被删除了(例如使用 shift()) ,以后的元素将被跳过函数

这里面感受最重要的是:ui

  • forEach 遍历的范围在第一次调用 callback 前就会肯定
  • 若是已经存在的值被改变,则传递给 callback 的值是 forEach 遍历到他们那一刻的值

看不懂? show me the codethis

// 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-3es5

// 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 次

好啦, 你听明白了嘛~

参考资料

相关文章
相关标签/搜索