Array.prototype
methods in JavaScriptThe 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:
toReversed()
methodtoSorted()
methodtoSpliced(start, deleteCount, ...items)
methodwith(index, value)
methodA 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:
toReversed()
methodThe 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"]
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
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
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:
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]
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]
As expected, all empty slots are returned as undefined
array item values.
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
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']
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" }
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]
The toReversed()
function produces a reversed array without creating a sparse array. As expected, the empty slots are returned as undefined
.
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]
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]
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]
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]
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]
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]
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']
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]
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)
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']
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']
toSpliced()
returns a new array without mutating the original array. Note how the syntax for both toSpliced()
and splice()
are identical.
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']
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']
with(index, value)
methodThe .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']
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"]
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']
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']
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.
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 see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.