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 次

好啦, 你聽明白了嘛~

參考資料

相關文章
相關標籤/搜索