Array#splice, the Swiss-army knife
Published on 7 May 2020
• 3 min
Cette page est également disponible en français.
Here comes the fourth article of our daily series: “19 nuggets of vanilla JS.” Today we’ll dive into an array method that’s been around almost forever (JS 1.2, 1997): Array#splice(…)
, a true Swiss-army knife of array tweaking.
The series of 19
Check out surrounding posts from the series:
- Efficiently deduplicating an array
- Efficiently extracting a substring
- Properly formatting a number
Array#splice
(this post)- Strings and Unicode
- Short-circuiting nested loops
- …and beyond! (fear not, all 19 are scheduled already)…
splice
or slice
?!
Don’t mistake one for the other.
slice([from[, until]])
is an idempotent / immutable operation: it doesn’t alter the original array but returns a new array based on the provided range.splice(from[, count[, item…]])
is a mutable operation, altering the original array by “replacing” a given segment with a new one. Sort of like RNA splicing (indeed, “splicing” means that sort of thing when you talk about genes, celluloid films or electrical wiring, to name a few). In short, we’re cut-and-pasting. It is the CRISPR of JavaScript arrays.
Also note that although slice
has an identical twin on the String
type, splice
doesn’t (because String
s are immutable).
For removing items
The “true” name of the second argument is deleteCount
. That says something! If you just go with two arguments (or even just one), splice
removes items, starting at position from
(first argument), which allows negative indices (like slice
).
- With a second, positive argument, it removes that number of items (stopping at the end of the array if it happens to be too short).
- Without a second argument, it removes the remainder of the array.
In all scenarios, splice
returns the removed segment, always as an array, regardless of whether there were zero, one or more items in it.
const awesomeDevs = ['Aurélie', 'Dan', 'Marie', 'Michel', 'Sara']
awesomeDevs.splice(2, 2) // => ['Marie', 'Michel']
awesomeDevs // => ['Aurélie', 'Dan', 'Sara']
awesomeDevs.splice(-2) // => ['Dan', 'Sara']
awesomeDevs // => ['Aurélie']
For replacing items
You can also provide, as individual arguments, items to insert as a replacement of those removed. What’s interesting is that there can be as many as you like: their number doesn’t have to match the amount of removed items!
const yummies = ['basil', 'chive', 'garlic', 'onion', 'shallot']
yummies.splice(-3, 2, 'parsley') // => ['garlic', 'onion']
yummies // => ['basil', 'chive', 'parsley', 'shallot']
yummies.splice(0, 3, 'garlic', 'onion')
// => ['basil', 'chive', 'parsley']
yummies // => ['garlic', 'onion', 'shallot']
What if the items you wish to replace with are available as an array? No worries, that’s what the spread operator is for:
const yummies = ['basil', 'chive', 'parsley', 'shallot']
const lovelyBulbs = ['garlic', 'onion']
yummies.splice(0, 3, ...lovelyBulbs)
yummies // => // => ['garlic', 'onion', 'shallot']
For inserting items
Astute reader, you probably realized that replacing items is a generic case:
- To remove stuff, just replace by… nothing.
- To insert stuff, just don’t remove (replace) anything first.
Indeed, if count
is zero but you do provide “replacers,” you end up—you guessed it—inserting.
const coolLanguages = ['javascript', 'rust']
coolLanguages.splice(0, 0, 'elixir', 'haskell') // => []
coolLanguages.splice(-1, 0, 'reason', 'ruby') // => []
coolLanguages
// => ['elixir', 'haskell', 'javascript', 'reason', 'ruby', 'rust']
Not on typed arrays…
You’re fresh, hip and rockin’ typed arrays? These have a fixed size, so splice
is one of the few methods from Array
you won’t find there.
…but works on array-likes
As do all Array
methods, splice
does not require it be called on a “true” array: the binding needs just be an “array-like,” which means it features a non-negative integer length
property and numeric properties between 0 and length - 1
.
I don’t believe you’d ever need this, but it’s still quirky-fun:
const blob = {
0: 'oh',
1: 'my',
2: 'gawd',
length: 3,
}
// OK, let’s call `Array#splice` on `blob`, with 4 arguments
Array.prototype.splice.call(blob, -2, 1, 'please', 'dear')
// => ['my']
blob
// => { 0: 'oh', 1: 'please', 2: 'dear', 3: 'gawd', length: 4 }
Bonus round: copyWithin
A fairly frequent tweaking use case is about copying part of an array elsewhere… in the same array. Internal copy-pasting, so to speak.
We could achieve that with splice
, assuming an extra slice
:
const lyrics = ['ra', 'ah', 'oh', 'ah', 'roma', 'oh', 'roma', 'ma']
lyrics.splice(1, 2, ...lyrics.slice(0, 2))
lyrics.splice(4, 2, ...lyrics.slice(3, 5))
lyrics // => [ 'ra', 'ra', 'ah', 'ah', 'ah', 'roma', 'roma', 'ma' ]
But that blows… Since ES2015, we’ve had copyWithin
for that scenario. The signature is copyWithin(to[, from[, until]])
. Careful, to
needs to be within the array’s boundaries (less than length
), otherwise nothing happens.
const lyrics = ['ra', 'ah', 'oh', 'ah', 'roma', 'oh', 'roma', 'ma']
lyrics.copyWithin(1, 0, 2)
lyrics.copyWithin(4, 3, 5)
lyrics // => [ 'ra', 'ra', 'ah', 'ah', 'ah', 'roma', 'roma', 'ma' ]
If that’s your need, then it can’t be beaten performance-wise, so there’s your freebie.
Want to dive deeper?
Our trainings are amazeballs, be they in-room or remote online, multi-client or in-house just for your company!