Search code examples
javascriptarraysiterationspecificationsdeterministic

Is the order of iteration for javascript array methods (map, forEach, reduce, etc) deterministic?


Is the order of iterating through an array using one of the native methods (map, forEach, reduce, filter, etc) deterministic and guaranteed by the standard?

EG, are foo, bar, baz, and qux guaranteed to be [0, 2, 6, 12]?

const a = [1, 2, 3, 4];
const foo = a.map((item, index) => item * index);
const bar = []; a.forEach((item, index) => bar[index] = item * index);
const baz = []; a.reduce((total, item, index) => baz[index] = item * index, 0);
const qux = []; a.filter((item, index) => qux[index] = item * index);
// etc

(These are (very) contrived examples)


Solution

  • The callback function is called for each element present in the array, in ascending order. It is not called for missing elements. (Missing elements? Yes, JavaScript handle sparse arrays)

    var test = [];
    test[30] = 'Test'; // sparse array, only one element defined.
    
    test.forEach(
      function(value){
        console.log(value); // will only be called one time.
      }
    );
    

    From the standard: ECMA-262

    22.1.3.10 Array.prototype.forEach ( callbackfn [ , thisArg ] )

    NOTE 1

    callbackfn should be a function that accepts three arguments. forEach calls callbackfn once for each element present in the array, in ascending order. callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array

    If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.

    callbackfn is called with three arguments: the value of the element, the index of the element, and the object being traversed.

    forEach does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.

    When the forEach method is called with one or two arguments, the following steps are taken:

    1. Let O be ? ToObject(this value).
    2. Let len be ? ToLength(? Get(O, "length")).
    3. If IsCallable(callbackfn) is false, throw a TypeError exception.
    4. If thisArg was supplied, let T be thisArg; else let T be undefined.
    5. Let k be 0.
    6. Repeat, while k < len a. Let Pk be ! ToString(k). b. Let kPresent be ? HasProperty(O, Pk). c. If kPresent is true, then i. Let kValue be ? Get(O, Pk). ii. Perform ? Call(callbackfn, T, « kValue, k, O »). d. Increase k by 1.
    7. Return undefined.