Search code examples
javascriptarraysreduceecmascript-5

targetArray.reduce behavior when targetArray.length is changed in the reducer function


This questions is primarily for those who work with the ECMAScript spec and/or implement it.

The spec specifies the algorithm for Array.prototype.reduce(callbackfn[,initialValue]). See that the algorithm, step 2, reads Let len be ? ToLength(? Get(O, "length")) and after that, my understanding is that len is never updated again. However, take a look a this:

const a = [0,1,2,3,4];
a.reduce(p=>{
  a.splice(0,1);
  console.log(a);
  return p;
},a);
// Logs:
// [1,2,3,4]
// [2,3,4]
// [3,4]
// and that's it

Since the iterations are supposed to run while k<len (k being the iteration counter), this behavior suggests that, in the implementations I tried (Chrome, Edge, Node), len is being updated at each iteration to lenght of a of the current iteration.

Is that really the intended behavior? Is there a concrete reason why len is purposely updated at each iteration instead of keeping it fixed from the start like specified in the spec?


Solution

  • The behaviour you are seeing is because of step 9.2: the callback is only called when the property is present on the object. It does iterate 5 times, but after you deleted 3 elements, in the fourth and fifth iteration a.hasOwnProperty(3) and a.hasOwnProperty(4) are false.

    We can best demonstrate this with a proxy:

    const a = [0,1,2,3,4];
    new Proxy(a, {
    	has(target, p) { const v = Reflect.has(target, p); console.log("has", p, v); return v },
    	get(target, p) { const v = Reflect.get(target, p); console.log("get", p, v); return v }
    }).reduce((p, e) => {
      a.length--;
      console.log("at " + e, a);
      return p;
    }, null);

    Or alternatively by calling reduce on an object whose .length changes don't affect the properties:

    const a ={0:0,1:1,2:2,3:3,4:4,length:5,reduce:Array.prototype.reduce};
    a.reduce((p, e) => {
      a.length--;
      console.log("at " + e, a);
      return p;
    }, null);