What are the consequences of mutating the array while applying Array.reduce to it

北城余情 提交于 2019-12-12 05:29:09

问题


Suppose I have an array:

const ar = [1,2,3,4];

And I apply reduce function to it and inside that function I remove elements like this:

ar.reduce((result, element, index, original)=>{
    original.pop();
}, []);

The function will be executed only two times for the first two elements. That's understandable since I've removed 3rd and 4th elements during previous calls.

But what is interesting is that if I execute the function and remove the current element:

ar.reduce((result, element, index, original)=>{
    original.splice(index, 1);
}, []);

the function is still executed two times for the first two elements. Why is it not executed for the 3rd and 4th elements since they remain in the array?

Is this behavior documented anywhere? In a spec maybe?


回答1:


In your second example, it is in fact executed for the 1st and 3rd elements, not for the first two:

const ar = [1, 2, 3, 4];

ar.reduce((result, element, index, original)=>{
    console.log(element, index);
    original.splice(index, 1);
}, []);

console.log(ar);
1 2 3 4
^

Here, while reduce's element is 1 and index is 0, it calls splice, removing the first element, then iterates to the next index:

2 3 4
  ^

Here, reduce's element is 3 and index is 1. After removing that, index will be equal to ar.length and it stops, leaving you with

2 4

The reason reduceRight() will still visit all the elements is because you iterate backwards, and the previous element positions are not affected by splicing the element at the current index:

const ar = [1, 2, 3, 4];

ar.reduceRight((result, element, index, original)=>{
    console.log(element, index);
    original.splice(index, 1);
}, []);

console.log(ar);

And the walkthrough:

element = 4, index = 3

1 2 3 4
      ^

element = 3, index = 2

1 2 3
    ^

element = 2, index = 1

1 2
  ^

element = 1, index = 0

1
^

To answer your question, yes ECMAScript documents this behavior for Array#reduce() as part of the specification:

The range of elements processed by reduce is set before the first call to callbackfn. Elements that are appended to the array after the call to reduce begins will not be visited by callbackfn. If existing elements of the array are changed, their value as passed to callbackfn will be the value at the time reduce visits them; elements that are deleted after the call to reduce begins and before being visited are not visited.

And the exact same paragraph as above applies to reduceRight as well.

Below is a polyfill for Array#reduce(), following the steps from the specification:

Object.defineProperty(Array.prototype, 'reduce', {
  configurable: true,
  writable: true,
  value: Array.prototype.reduce || function reduce(callbackfn) {
    "use strict";
    // 1.
    if (this === undefined || this === null) {
      throw new TypeError("Array.prototype.reduce called on null or undefined");
    }
    let O = Object(this);
    // 2.
    let len = ToLength(O.length);
    // 3.
    if (typeof callbackfn != 'function') {
      throw new TypeError(`${String(callbackfn)} is not a function`);
    }
    // 4.
    if (len == 0 && arguments.length < 2) {
      throw new TypeError("Reduce of empty array with no initial value");
    }
    // 5.
    let k = 0;

    let accumulator;
    // 6.
    if (arguments.length >= 2) {
      // a.
      accumulator = arguments[1];
    // 7.
    } else {
      // a.
      let kPresent = false;
      // b.
      while (!kPresent && k < len) {
        // i.
        let Pk = String(k);
        // ii.
        kPresent = Pk in O;
        // iii.
        if (kPresent) accumulator = O[Pk]; // 1.
        // iv.
        k++;
      }
      // c.
      if (!kPresent) throw new TypeError("Reduce of empty array with no initial value");
    }
    // 8.
    while (k < len) {
      // a.
      let Pk = String(k);
      // b.
      let kPresent = Pk in O;
      // c.
      if (kPresent) {
        // i.
        let kValue = O[Pk];
        // ii.
        accumulator = callbackfn(accumulator, kValue, k, O);
      }
      // d.
      k++;
    }
    // 9.
    return accumulator;
  }
});

function ToInteger(argument) {
  let number = Number(argument);

  if (isNaN(number)) return 0;

  switch (number) {
  case 0:
  case Infinity:
  case -Infinity:
    return number;
  }

  return parseInt(number);
}

function ToLength(argument) {
  let len = ToInteger(argument);

  if (len <= 0) return 0;
  if (len == Infinity) return Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1;

  return len;
}


来源:https://stackoverflow.com/questions/44682323/what-are-the-consequences-of-mutating-the-array-while-applying-array-reduce-to-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!