DEV Community

Cover image for A guide to the 4 new Array.prototype methods in JavaScript
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

A guide to the 4 new Array.prototype methods in JavaScript

Written by Ohans Emmanuel✏️

The latest version of the JavaScript language standard is ECMAScript 2023, which is the 14th edition. This update includes new methods on the Array prototype.

I’ll guide you through the prominent four new methods in this article, including their behavior with sparse arrays and array-like objects. If you’re a fan of a declarative, functional style of writing JavaScript programs, you’re in for a treat.

Dive in:

Is it important to preserve the original array without any mutations?

A common theme with the four new array methods is the focus on not mutating the original array, but returning a completely new array. You may wonder, why is this behavior significant?

Generally speaking, there are numerous advantages to leaving data unmodified, as demonstrated by these four new array methods. These benefits are not limited to arrays, but rather extend to all JavaScript objects.

Although there are many benefits, some of the most significant ones are outlined below:

  • Pure functions: In functional programming, pure functions are functions that always produce the same output when given the same input: they don’t have any side effects and their behavior is predictable. Working with this functional mental model is ideal when you're not modifying data, and these four new array methods are a great addition for this reason
  • Predictable state management: Creating new copies of our state object (or array) makes state management more predictable by eliminating unexpected changes and representing the data at a specific point in time with new copies. This simplifies managing the state at scale and improves reasoning about state management in general
  • Change detection: Frameworks like React use simplified change detection by comparing two copies of the state or props object to identify any alterations and render the user interface accordingly. Detecting changes becomes simpler with these methods, as we can compare the two objects at any given moment to identify any alterations

The toReversed() method

The toReversed() method is similar to the classic reverse() method, but with a significant distinction. toReversed() reverses the elements in an array, without mutating the original array.

Consider the following array of fruits below:

const fruits = ["🍎apple", "🍊orange", "🍌banana"]
Enter fullscreen mode Exit fullscreen mode

Now, reverse fruits with .reverse():

// Reverse the array 
const result = fruits.reverse()
console.log(result) 

// ['🍌banana', '🍊orange', '🍎apple']

console.log(fruits)
// ['🍌banana', '🍊orange', '🍎apple']
// ↗️ original array is mutated
Enter fullscreen mode Exit fullscreen mode

With reverse(), the original array is mutated.

To reverse the array without mutating it, we can use the toReversed() method as demonstrated below:

// Reverse the array 
const result = fruits.toReversed()
console.log(result) 

// ['🍌banana', '🍊orange', '🍎apple']

console.log(fruits)
// ["🍎apple", "🍊orange", "🍌banana"] 
// ↗️ original array is preserved
Enter fullscreen mode Exit fullscreen mode

Voilà!

If you’re using the latest version of a current browser like Chrome, you can access your browser console and test out the code examples provided in the article: Trying the toReversed method in the browser console

Behavior with sparse arrays

For a quick refresher, sparse arrays are arrays without sequential elements. For example, consider the following:

const numbers = [1,2,3]
// Assign an item to index 11
numbers[11] = 12

console.log(numbers)
// [1, 2, 3, empty × 8, 12]
Enter fullscreen mode Exit fullscreen mode

In the example above, numbers has eight empty item slots. numbers is a sparse array. Now, back to toReversed(). How does this work with sparse arrays?

toReversed() never returns a sparse array. If the original array had empty slots, they would be returned as undefined.

Consider calling toReversed() on the numbers array below:

const numbers = [1,2,3]
// Assign an item to index 11
numbers[11] = 12

numbers.toReversed()
// [12, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 3, 2, 1]
Enter fullscreen mode Exit fullscreen mode

As expected, all empty slots are returned as undefined array item values.

Behavior with array-like objects

Even though toReversed() exists specifically on the Array prototype, it may also be invoked on array-like objects.

An array-like object typically has a length property and, optionally, properties with integer index names. String objects are an example of array-like objects.

The function toReversed() first reads the length property of the object it is called on and then iterates through the integer keys of the object from the end to the start, which means from length - 1 to 0. It adds the value of each property to the end of a new array, which is then returned.

Let’s give this a try. Consider the wrong application of toReversed() on a string:

const s = "Ohans Emmanuel"

// call `toReversed` directly on the string
s.toReversed()

//Uncaught TypeError: s.toReversed is not a function
Enter fullscreen mode Exit fullscreen mode

Even though a string object is an array-like object, this program is wrong: we cannot invoke it in the manner string.toReversed() because toReversed doesn’t exist on the string prototype.

However, we may use the call() method as shown below:

const s = "Ohans Emmanuel"

// Array.prototype.toReversed.call(arrayLike)
Array.prototype.toReversed.call(s)

//['l', 'e', 'u', 'n', 'a', 'm', 'm', 'E', ' ', 's', 'n', 'a', 'h', 'O']
Enter fullscreen mode Exit fullscreen mode

How about a self-constructed, array-like object? Consider the example below:

// Has a length property and integer index property.
const arrayLike = {
 length: 5,
 2: "Item #2"
}
Enter fullscreen mode Exit fullscreen mode

If this were a standard array, it would be a sparse array, i.e., of length five and a value in the second index.

Consider the result of calling toReversed on this:

console.log(Array.prototype.toReversed.call(arrayLike))

// [undefined, undefined, 'Item #2', undefined, undefined]
Enter fullscreen mode Exit fullscreen mode

The toReversed() function produces a reversed array without creating a sparse array. As expected, the empty slots are returned as undefined.

The toSorted() method

.toSorted() is the counterpart to the classic .sort() method.

As you may have guessed, unlike .sort(), .toSorted() will not mutate the original array. Consider the basic sort operation with .sort() below:

const list = [1, 5, 6, 3, 7, 8, 3, 7]
//Sort in ascending order 
const result = list.sort()

console.log(result)
// [1, 3, 3, 5, 6, 7, 7, 8]
console.log(list)
// [1, 3, 3, 5, 6, 7, 7, 8]
Enter fullscreen mode Exit fullscreen mode

As shown above, sort() sorts the array in place and consequently mutates the array. Now, consider the same with toSorted():

const list = [1, 5, 6, 3, 7, 8, 3, 7]
// Sort in ascending order 
const result = list.toSorted()

console.log(result)
// [1, 3, 3, 5, 6, 7, 7, 8]
console.log(list)
// [1, 5, 6, 3, 7, 8, 3, 7]
Enter fullscreen mode Exit fullscreen mode

As seen above, toSorted() returns a new array with the elements sorted.

Note that toSorted() retains the same syntax as sort(). For example, we may specify a function defining the sort order e.g., list.toSorted(compareFn).

Consider the example below:

const list = [1, 5, 6, 3, 7, 8, 3, 7]
//Sort the array in descending order 
list.toSorted((a,b) => a < b ? 1 : -1)
// [8, 7, 7, 6, 5, 3, 3, 1]
Enter fullscreen mode Exit fullscreen mode

Behavior with sparse arrays

Empty slots will always be returned as undefined. In fact, they are treated as if they had a value of undefined. However, the compareFn will not be invoked for these slots and they’ll always come at the end of the returned array.

Consider the following example with an array with an empty first slot:

// Note the empty initial slot 
const fruits = [, "🍎apple", "🍊orange", "🍌banana"]

console.log(fruits.toSorted())

// ['🍊orange', '🍌banana', '🍎apple', undefined]
Enter fullscreen mode Exit fullscreen mode

This behavior is identical to what would happen if the initial value were undefined.

Consider the example below:

const fruits = [undefined, "🍎apple", "🍊orange", "🍌banana"]

console.log(fruits.toSorted())

// ['🍊orange', '🍌banana', '🍎apple', undefined]
Enter fullscreen mode Exit fullscreen mode

Also, note that the empty slots (or undefined slots) will always be moved to the end of the returned array, regardless of their position in the original array.

Consider the following example:

// empty slot is in index 2
const fruits = ["🍎apple", "🍊orange", , "🍌banana"]

console.log(fruits.toSorted())

// returned last 
// ['🍊orange', '🍌banana', '🍎apple', undefined]

// undefined value is in index 2

const otherFruits = ["🍎apple", "🍊orange", undefined , "🍌banana"]

console.log(otherFruits.toSorted())

// returned last 
// ['🍊orange', '🍌banana', '🍎apple', undefined]
Enter fullscreen mode Exit fullscreen mode

Behavior with array-like objects

When using the toSorted() function with objects, it will first read the length property of the this object. It will then collect the object's integer keys from the start to the end, which is from 0 to length - 1. After sorting them, it will return the corresponding values in a new array.

Consider the following example with a string:

const s = "Ohans Emmanuel"

// Array.prototype.toSorted.call(arrayLike)
Array.prototype.toSorted.call(s)
(14) [' ', 'E', 'O', 'a', 'a', 'e', 'h', 'l', 'm', 'm', 'n', 'n', 's', 'u']
Enter fullscreen mode Exit fullscreen mode

Consider the following example with a constructed array-like object:

// Has a length property and integer index property.
const arrayLike = {
 length: 5,
 2: "Item #2"
10: "Out of bound Item" // This will be ignored since the length is 5
}

console.log(Array.prototype.toSorted.call(arrayLike))
// ['Item #2', undefined, undefined, undefined, undefined]
Enter fullscreen mode Exit fullscreen mode

The toSpliced(start, deleteCount, ...items) method

.toSpliced() is the counterpart to the classic .splice() method. As with the other new methods we’ve covered, toSpliced() will not mutate the array it is invoked on, unlike .splice().

The syntax for toSpliced is identical to .splice, as shown below:

toSpliced(start)
toSpliced(start, deleteCount)
toSpliced(start, deleteCount, item1)
toSpliced(start, deleteCount, item1, item2, itemN)
Enter fullscreen mode Exit fullscreen mode

Add a new array item with the classic .splice(), as shown below:

const months = ["Feb", "Mar", "Apr", "May"] 
// Insert item "Jan" at index 0 and delete 0 items
months.splice(0, 0, "Jan")

console.log(months) 
//  ['Jan', 'Feb', 'Mar', 'Apr', 'May']
Enter fullscreen mode Exit fullscreen mode

splice() inserts the new array item and mutates the original array. To create a new array without mutating the original array, use toSpliced().

Consider the example above rewritten to use toSpliced():

const months = ["Feb", "Mar", "Apr", "May"] 
// Insert item "Jan" at index 0 and delete 0 items
const updatedMonths = months.toSpliced(0, 0, "Jan")

console.log(updatedMonths)
// ['Jan', 'Feb', 'Mar', 'Apr', 'May']
console.log(months)
// ['Feb', 'Mar', 'Apr', 'May']
Enter fullscreen mode Exit fullscreen mode

toSpliced() returns a new array without mutating the original array. Note how the syntax for both toSpliced() and splice() are identical.

Behavior with sparse arrays

toSpliced() never returns a sparse array. As such, empty slots will be returned as undefined. Consider the example below:

const arr = ["Mon", , "Wed", "Thur", , "Sat"];
// Start at index 1, and delete 2 items
console.log(arr.toSpliced(1, 2)); 

// ['Mon', 'Thur', undefined, 'Sat']
Enter fullscreen mode Exit fullscreen mode

Behavior with array-like objects

With array-like objects, toSpliced gets the length of the this object, reads the integer key needed, and writes the result to a new array:

const s = "Ohans Emmanuel"

// Start at index 0, delete 1 item, insert the other items
console.log(Array.prototype.toSpliced.call(s, 0, 1, 2, 3));

// [2, 3, 'h', 'a', 'n', 's', ' ', 'E', 'm', 'm', 'a', 'n', 'u', 'e', 'l']
Enter fullscreen mode Exit fullscreen mode

The with(index, value) method

The .with() array method is particularly interesting. First, consider the bracket notation for changing the value of a specific array index:

const favorites = ["Dogs", "Cats"]
favorites[0] = "Lions"

console.log(favorites)
//(2) ['Lions', 'Cats']
Enter fullscreen mode Exit fullscreen mode

With the bracket notation, the original array is always mutated. .with() achieves the same result of inserting an element in a specific index, but does not mutate the array. Instead, it returns a new array with the replaced index.

Let’s rewrite the initial example to use .with():

const favorites = ["Dogs", "Cats"]
const result = favorites.with(0, "Lions")

console.log(result)
// ['Lions', 'Cats']
console.log(favorites)
// ["Dogs", "Cats"]
Enter fullscreen mode Exit fullscreen mode

Behavior with sparse arrays

with() never returns a sparse array. As such, empty slots will be returned as undefined:

const arr = ["Mon", , "Wed", "Thur", , "Sat"];
arr.with(0, 2)
// [2, undefined, 'Wed', 'Thur', undefined, 'Sat']
Enter fullscreen mode Exit fullscreen mode

Behavior with array-like objects

Similar to other methods, with() reads the length property of the this object. It then reads every positive integer index (less than the length) of the object. As these are accessed, it saves their property values to the return array index.

Finally, the index and value in the call signature with(index, value) are set on the returned array. Consider the example below:

const s = "Ohans Emmanuel"

// Set the value of the first item
console.log(Array.prototype.with.call(s, 0, "F"));

// ['F', 'h', 'a', 'n', 's', ' ', 'E', 'm', 'm', 'a', 'n', 'u', 'e', 'l']
Enter fullscreen mode Exit fullscreen mode

Conclusion

The ECMAScript standard keeps improving, and taking advantage of its new features is a good idea. Go ahead and leverage toReversed, toSorted, toSpliced, and with to create more declarative JavaScript applications.


LogRocket: Debug JavaScript errors more easily by understanding the context

Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

LogRocket Signup

LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

Try it for free.

Top comments (5)

Collapse
 
manchicken profile image
Mike Stemle

I love with() function. I’m gonna have to look into that more.

Collapse
 
artydev profile image
artydev

Very instructive, thank you :-)

Collapse
 
fruntend profile image
fruntend

Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍

Collapse
 
davboy profile image
Daithi O’Baoill

Thank you

Collapse
 
mohiyaddeen7 profile image
mohiyaddeen7

Thank you for your clear explanation 👍