原文連接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 循環, 或許徹底不同. 那麼 forEach
的 callback
到底執行了多少次呢?github
這樣的事情固然要看規範了, Array.prototype.forEach() 中文數組
forEach 方法按升序爲數組中含有效值的每一項執行一次callback 函數,那些已刪除(使用delete方法等狀況)或者未初始化的項將被跳過(但不包括那些值爲 undefined 的項)(例如在稀疏數組上)。bash
forEach 遍歷的範圍在第一次調用 callback 前就會肯定。調用forEach 後添加到數組中的項不會被 callback 訪問到。若是已經存在的值被改變,則傳遞給 callback 的值是 forEach 遍歷到他們那一刻的值。已刪除的項不會被遍歷到。若是已訪問的元素在迭代時被刪除了(例如使用 shift()) ,以後的元素將被跳過函數
這裏面感受最重要的是:ui
看不懂? 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 次
好啦, 你聽明白了嘛~
參考資料