The ECMAScript 2023 specification has been recently finalised. It includes some new methods on the Array object that will help make our JavaScript programs more predictable and maintainable. The methods toSorted
, toReversed
, toSpliced
, and with
allow you to perform operations on arrays by without changing the data in place, but by making a copy and changing that copy. Read on to learn the difference and how to start using them in your projects.
Mutation and side effects
The Array object has always had some oddities. Methods like sort
, reverse
, and splice
change the array in place. Other methods like concat
, map
, and filter
create a copy of the array and then operate on the copy. When you perform an operation on an object that mutates it, that is a side effect and can cause unexpected behaviour elsewhere in your system.
As an example, this is what happens when you reverse an array.
As you can see, the original array was reversed and even though we assigned the result of reversing the array to a new variable, both variables simply point to the same array.
Mutating arrays and React
One of the best known issues with array methods that mutate the array is when you use them in a React component. You can't mutate an array and then try to set it as a new state because the array itself is the same object and this won't trigger a new render. Instead you need to copy the array first, then mutate the copy and set that as the new state. The React docs have a whole page explaining how to update arrays in state because of this.
Copy first, then mutate
The way to work around this has been to copy the array first, then mutate it. There are several different ways to make a copy of an array, including: Array.from
, the spread operator, or calling the slice
function with no arguments.
It's great that there is a workaround, but having to remember to perform one of the different copy methods first isn't great.
New methods change by copy
That's where the new methods come in. Each of toSorted
, toReversed
, toSpliced
, and with
copy the original array for you, change the copy and return it. It will make performing each of these actions easier to write as you only need to remember to call one function and easier to read as you don't need to parse one of four methods of copying an array first. So what do each of the methods do?
Array.prototype.toSorted
The toSorted
function returns a new, sorted array.
The sort
function has some unexpected behaviour and aside from copying, toSorted
shares that behaviour. You still need to be careful if you are sorting numbers or strings with accented characters. Make sure you provide a comparator function (like String
's localeCompare
) that will produce the results you are looking for.
Array.prototype.toReversed
Using the toReversed
function returns a new array sorted in the reverse order.
Sonar has a rule that covers misleading use of methods like reverse
. Assigning the result of reverse
to a new variable is misleading because the original array was mutated too. Now you can use toReversed
or toSorted
to copy the array and mutate the copy instead..
Array.prototype.toSpliced
The toSpliced
function is a bit different to its original version splice
. splice
changes the existing array by deleting and adding elements at the provided index and returns an array containing the deleted elements from the array. toSpliced
returns a new array without the removed elements and including any added elements. Here's how it works:
If you are using splice
for its return value, then toSpliced
will not be a drop in replacement. If you want to know the deleted elements without altering the original array, then you should use the copying method slice
.
Frustratingly, splice
takes different arguments to slice
. splice
takes an index and the number of elements after that index to remove and slice
takes two indexes, the start and the end. If you wanted to use toSpliced
in place of splice
but also get the elements that are deleted you could apply toSpliced
and slice
to the original array, like this:
Array.prototype.with
The with
function is the copy equivalent of using square bracket notation to change one element of an array. So, instead of directly changing the array like this:
You can copy the array and make the change
Not just arrays
The regular array object isn't the only one benefitting from these new methods. You can also use toSorted
, toReversed
, and with
on any TypedArray
. That is everything from Int8Array
to BigUint64Array
. TypedArray
s do not have a splice
method, so they are not getting a matching toSpliced
method.
Caveats
I mentioned at the top that methods like map
, filter
, and concat
already perform copying operations. There is a difference between those methods and the new copying methods though. If you extend the built in Array
object and use map
, flatMap
, filter
, or concat
on an instance, it will return a new instance of the same type. If you extend an Array
and use toSorted
, toReversed
, toSpliced
, or with
the result will be a plain Array
again.
You can turn that back into your custom Array
with the use of MyArray.from
:
Support
While the ECMAScript 2023 spec is very new, there is already good support for these new array methods. Chrome 110, Safari 16.3, Node.js 20, and Deno 1.31 all support all four methods and there are polyfills and shims available for platforms that don't yet have support.
JavaScript keeps improving
It's great to see additions like this to the ECMAScript standard that make it easier for us to write predictable code. There are a few other proposals that made it into ES2023 that you should check out if you're interested. Check out the whole TC39 proposals repository if you want to see what else is close to joining the spec.