The mutating methods change this in place: push, pop, shift, unshift, splice, sort, reverse, fill and copyWithin. Most return something other than the array (push returns the new length, splice returns the removed items), but sort, reverse, fill and copyWithin return the same array, which makes it easy to forget they mutated it.
ES2023 added four copying counterparts:
| Mutating | Copying |
|---|---|
reverse() |
toReversed() |
sort(cmp) |
toSorted(cmp) |
splice(start, n, ...items) |
toSpliced(start, n, ...items) |
arr[i] = v |
with(i, v) |
For the rest, MDN's suggested replacements are slice (for pop and shift) and concat (for push), or copy first and then mutate: [...arr].fill(0).
Details worth knowing:
- Copies are shallow: the new array holds the same object references.
- The new methods never touch
this, so they work on frozen arrays.sort()on a frozen array throws aTypeError. - They always build a plain
Array(spec:ArrayCreate), ignoringconstructorandSymbol.species, unlikeslice,maporfilter. - They read every index with
Get, so holes come out asundefined. The olderreverseandsplicepreserve holes. withaccepts negative indices, but throws aRangeErrorwhen the index is out of bounds, unlikearr[10] = v, which extends the array.
Why it matters: React state, Redux reducers and useMemo inputs depend on reference equality, so state.sort() followed by setState(state) does not re-render and corrupts the previous state.