Array.prototype.remove

JavaScript only has one function for removing an arbitrary item from an Array (splice). While splice is more versatile than a simple remove method, it has two drawbacks:

  1. It isn't very intuitive for new programmers.
  2. Code that moves items from one list to another is unnecessarily verbose.

Compare:

const item = list.splice(index, 1)[0];
otherList.push(item);

to

const item = list.remove(index);
otherList.push(item);

Would it be reasonable to add a remove(index) function to Array that works the same as List.remove in Java?

EDIT: As noted below, it would be logical to include Array.prototype.insert in the proposal as well.

2 Likes

It doesn't seem like a good idea to add another mutating method to Array.prototype - this code seems like it'd be better with .filter rather than mutation.

Hi, ljharb. I know it's hard to tell without more context, but the above example isn't trying to create a specific subset of the original data, so I don't think a filter would be idiomatic (or efficient) here.

The use case is specifically about a user choosing specific items to move to another list (like moving an item to a shopping cart or dropping equipment in a video game), so mutation is necessary and intentional.

We have

  • .shift() and .unshift(...items) for removing and adding items at the start of the array;
  • .pop() and .push(...items) for removing and adding items at the end of the array;

it seems reasonable to have

  • .remove(index) and .insert(index, ...items) for removing and adding items in the middle of the array.

Having scan my code, I have found the following uses of .splice():

  1. removing one item in the middle of the list;
  2. removing all items from a specific position to the end of the list;
  3. retrieving and removing a bunch of items at the beginning of the list;
  4. adding one element in the middle of the list;
  5. adding several elements in the middle of the list;
  6. replacing one element at a specific position of the list with a variable number of elements;

One specific issue of .splice is that it is commonly used for two opposite operations, namely removing and inserting, and you have to look at the value of a specific argument, which is often 1 or 0, in order to know what it does. Returning to my use cases above let’s compare:

  • (1) list.splice(index, 1) vs. list.remove(index)
  • (4) list.splice(index, 0, [item]) vs. list.insert(index, item)
  • (5) list.splice(index, 0, items) vs. list.insert(index, ...items)

and, again, consider the symmetry between .shift(), pop() and .remove() on the one hand, and .unshift(), .push() and .insert() on the other hand.

3 Likes

I actually almost never use .splice() (I might use it for removing items, but I generally don't use it for insertion). I tend to prefer using other array methods to achieve the same result.

These are generally the solutions I reach for to avoid splice():

// removing one item in the middle of the list; (though I would probably use .splice() here)
myList = [...myList.slice(0, 2), ...myList.slice(3)]

// removing all items from a specific position to the end of the list;
myList = myList.slice(2)

// retrieving and removing a bunch of items at the beginning of the list;
const front = myList.slice(0, 2)
myList = myList.slice(2)

// adding one element in the middle of the list;
myList = [...myList.slice(0, 2), 'x', ...myList.slice(2)]

// adding several elements in the middle of the list;
myList = [...myList.slice(0, 2), 'x', 'y', 'z' ,...myList.slice(2)]

// replacing one element at a specific position of the list with a variable number of elements;
myList = [...myList.slice(0, 2), 'x', 'y', 'z' ,...myList.slice(3)]

The above solutions have the nice benefit that they don't modify the original array, and can be used to make functions more pure. If the performance is needed, then a .remove()/.insert() could be faster, although in-place modifications in the middle of an array is still a slow operation, and code that really needs the performance will sometimes be better off reaching for a linked-list.

I also find myself often creating little helper functions at the top of files where I need additional array operations that JavaScript doesn't provide. e.g.

const insertIntoArray = (array, index, items) => items.splice(index, 0, ...items)

The addition of .remove()/.insert() would be helpful and nice (Users of other languages would expect to find these methods, .splice() isn't readable, it can be more performant, it'll pair well with the existing .push()/.pop()/.shift()/.unshift() methods, etc). In general, I would welcome this addition to the language, but, at least for me in practice, I don't think I would use it all that much over the pure solutions I'm currently using instead.

claudepache, would you be my champion for this?

I’m not part of TC39; you should find a TC39 member that supports it. But I can participate in writing the proposal.

Could you take another look at this? Other commenters have given some compelling reasons to include it in the language.

I had the same thoughts as you about the splice method, and actually I'd be curious to know why, back in the days this was implemented, they chose this approach instead of the classic insert / remove / replace methods!

Also, I'd like to propose alternative names for those methods:

  • removeAt instead of remove
  • insertAt instead of insert

The benefits I see is that we directly know we are dealing with indexes, especially for the remove method. For the insert method, this would enforce the idea that the index comes first, so that we no longer have doubts whether it is insert(index, element) or insert(element, index).

Also, this could leave room for a potential remove(item) method, that would remove the first occurrence of the given item in the array, like in python. Such a function would also be really great, as for now we have to do it in two steps:

  • first, store the element index in the array with indexOf
  • then, splice at the given index if the index is not -1, because else it would remove the last item (gotcha!)

Such a remove(item) method would ease removing items from an array by a lot.

Finally, I'd like to propose that for the ones that need or want to not mutate the original array, we could create these methods alongside the Change Array by copy proposal, so we would also have the corresponding methods:

  • insertedAt(index, ...items) -> Array
  • removedAt(index) -> Array
  • removed(item) -> Array

What do you think about all of this ?

3 Likes

Yes - insertAt and removeAt are much better names. One of my hesitations with remove() was that it wasn't clear what type of argument it expected - python's remove() was called the same thing but meant something different. It quickly starts to become confusing which methods do what.

I presume the reason .splice() exists the way it does was for performance. If you have to insert something in the middle of an array, you need to shift everything in front of that element forward to make room for it. Similarly, removing an array element shifts everything after it to the left. Javascript has to do half the work if you do both operations at the same time in splice(). In practice, this isn't very common to do.

I would rather have the immutable versions in the language over the mutable ones, (so I would be happy if those were the only ones that made it in), but I'm not against the mutable ones.

Clemdz, I agree that removeAt and insertAt with the possibility of remove(item) sound much better than remove/insert, and having the side-effect free versions sounds reasonable too.

Regarding Change Array by Copy: Are you saying to combine the proposals? I could see "mixed concerns" being a possible objection.

No no, when I say creating these methods alongside the Change Array by copy proposal, I mean: keep track of the advancement of the Change Array by copy proposal, to see how it evolves along the different TC39 stages, and adapt the immutable versions of this proposal accordingly! :)

Got it. That sounds like a good idea.

I don't see it as a performance argument so much as a flexibility argument: one method to rule them all.

I'd still like a way to filter in-place or at the very least remove individual values - it'd remove the need for a lot of for loops I write.