Change array by copy


These methods are supported in Node (since version 20), Deno, Bun, Safari (since version 16), Chrome/Edge (from version 110) and Firefox (since version 115).

Immutability

In JavaScript, arrays are mutable. Certain methods change the array:

const names = ['Xavier', 'Anna', 'Zach', 'Brad'];
names.sort();
// names is now ['Anna', 'Brad', 'Xavier', 'Zach']
names.reverse();
// names is now ['Zach', 'Xavier', 'Brad', 'Anna']

Here’s the full list of array methods that mutate the array:

  • push
  • pop
  • splice
  • shift
  • unshift
  • reverse
  • sort
  • copyWithin
  • fill

Tuples

There’s a proposal for adding tuples to JavaScript. Tuples look identical to arrays except that they begin with a hash # symbol:

const users = #['Xavier', 'Anna', 'Zach', 'Brad'];

The key difference between arrays and tuples: tuples are immutable. Tuples have all the methods that Arrays have, except for the destructive ones. Because tuples can’t be mutated, they can’t have any of the methods listed above.

Some new non-destructive methods have been added that will work with both Tuples and Arrays.

There are now immutable equivalents to the reverse, sort, and splice methods that will return a new copy of the array with the changes applied, leaving the original array unchanged:

  • .toReversed()
  • .toSorted()
  • .toSpliced()

There’s also .with(), which doesn’t have an older equivalent.

Here’s the official motivation for adding them, as explained by members of the TC39:

The Tuple.prototype introduces these functions as a way to deal with the immutable aspect of the Tuples in Record & Tuple. While Arrays are not immutable by nature, this style of programming can be beneficial to users dealing with frozen arrays for instance. This proposal notably makes it easier to write code able to deal with Arrays and Tuples interchangeably.

Immutable array methods

A non-mutating equivalent to shift, pop, push and fill can already be written in a relatively simple way.

  • pop can be replaced with .slice(-1).
  • push can be replaced with .concat()
  • shift can be replaced with .slice(1)
  • fill can be replaced by .map()

It was already possible to use reverse and sort without mutating the original array by using spread:

    const names = ['Xavier', 'Anna', 'Zach', 'Brad'];
    const sortedNames = [...names].sort();
    const invertedNames = [...names].reverse();

The new functions offer a slightly better way to achieve this.

.toReversed

    const names = ['Xavier', 'Anna', 'Zach', 'Brad'];
    const reversedNames = names.toReversed();
    // names will remain ['Xavier', 'Anna', 'Zach', 'Brad']
    // reversedNames is ['Brad', 'Zach', 'Anna', 'Xavier']

.toSorted()

toSorted works just like sort() but it returns a new array. The original array is not changed when you call toSorted().

const names = ['Xavier', 'Anna', 'Zach', 'Brad'];
const sortedNames = names.toSorted();
// names will remain ['Xavier', 'Anna', 'Zach', 'Brad']
// sortedNames is ['Anna', 'Brad', 'Xavier', 'Zach']

.toSpliced()

The first argument is a start index. The second is the amount of array items you want to delete. You can then specify one or multiple elements to add at the start index.

const names = ['Xavier', 'Anna', 'Zach', 'Brad'];
const differentNames = names.toSpliced(2, Infinity, 'Mark');
// Infinity means all array items starting from the second index get deleted. 'Mark' gets added to the array so the new array is ['Xavier', 'Anna', 'Mark']

Here’s an non-mutating equivalent of unshift using toSpliced:

const names = ['Xavier', 'Anna', 'Zach', 'Brad'];
const moreNames = names.toSpliced(0, 0, 'Mark', 'Luke', 'John');
// 'Mark', 'Luke' and 'John' are added to the beginning of the array.

.with()

with() accepts an index and a value. The value replaces the array item at the given index.

const names = ['Xavier', 'Anna', 'Zach', 'Brad'];
const namesWithOneChanged = names.with(2, 'Mark');

In the new array, ‘Mark’ replaces ‘Zach’.