问题
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 tocallbackfn
. Elements that are appended to the array after the call toreduce
begins will not be visited bycallbackfn
. If existing elements of the array are changed, their value as passed tocallbackfn
will be the value at the time reduce visits them; elements that are deleted after the call toreduce
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