JavaScript Array Remove
原文地址: http://ejohn.org/blog/javascript-array-remove/I have another handy method, that I recently developed, that allows you to simply remove an item - or a group of items - from an array. Like with my implementation of JavaScript Method Overloading I wanted something that was concise, elegant, speedy, and highly effective.
So here's the method that I came up with:
// Array Remove - By John Resig (MIT Licensed)
Array. prototype. remove = function (from, to ) {
var rest = this. slice ( (to || from ) + 1 || this. length );
this. length = from < 0 ? this. length + from : from;
return this. push. apply ( this, rest );
};
and here's some examples of how it could be used:
// Remove the second item from the array
array. remove ( 1 );
// Remove the second-to-last item from the array
array. remove (- 2 );
// Remove the second and third items from the array
array. remove ( 1, 2 );
// Remove the last and second-to-last items from the array
array. remove (- 2,- 1 );
I extend that native Array prototype, if you don't want to extend a global object, you can do something like the following, instead:
// Array Remove - By John Resig (MIT Licensed)
Array. remove = function (array, from, to ) {
var rest = array. slice ( (to || from ) + 1 || array. length );
array. length = from < 0 ? array. length + from : from;
return array. push. apply (array, rest );
};
Here's a couple goals that I had for the method:
- It had to add an extra method to an array object that would allow me to remove an item by index (e.g. array.remove(1) to remove the second item).
- I wanted to be able to remove items by negative index (e.g. array.remove(-1) to remove the last item in the array).
- I wanted to be able to remove a group of items by index, and negative index (e.g. array.remove(0,2) to remove the first three items and array.remove(-2,-1) to remove the last two items).
- It had to be destructive (modifying the original array).
- It had to behave like other destructive array methods (returning the new array length - like how push and unshift work).
The above code may appear to be simple, due to its trivial length, but once again, in order to make it happen, I made good use of some lesser-known JavaScript features that really need to be explained.
To start with, most "remove" methods that you'll find on the Internet end up making use of two slice operations (and a concat) in order to compose the final result, like so:
array = array. slice ( 0,i ). concat ( array. slice (i+ 1 ) );
(The above composes all of the items before the item that's to be removed together with all the items after it into a single array.) This can end up becoming quite costly as slice and concat operations aren't terribly fast. We can circumvent this by doing a single slice operation and doing a push instead. This is where the trickiness comes in.
Now, we know that the front of the array is never going to change (even if the "front" contains no elements, there's no need for us to do an additional slice operation). Thus, making that assumption, we can do this trick:
array. length = i - 1;
array. push. apply ( array, slicedResults );
Here's what that does:
- Modifying the length of an array effectively removes all results from the end of it. If length or elegance isn't an issue for you, you could modify the above method to check for calls like .remove(-2) and optimize it to be a single operation: this.length += from;
- Thus, since we've already removed all the dangling items from the array (along with the items that we wanted to remove, to begin with) we need to merge the sliced items back onto the original array.
- An array's push method is capable of taking any number of arguments (meaning statements like this are valid: array.push(1,2,3,4)). Thus, if we call that method using apply and put in the sliced array all of the resulting items will be "concatenated" onto the end of the array, in one, single super-fast operation.
Fun Fact: I use this technique to construct the array-like results of DOM elements in a jQuery object.
The only other tricky bit of the function is the following snippet:
(to || from ) + 1 || this. length
"(to || from) + 1" is, effectively, saying "start the slice just after where the remove operation ended". Thus, if you did .remove(1,2) we'd need to start the slice at the index of 3. However, if we just did .remove(1) we'd start the slice at 2. We get to cheat a little bit since a to of 0 is irrelevant and can be ignored.
Now, the second part, "|| this.length" is just a tricky way of saying "if the end index was -1, make sure that we don't start the slice operation at 0, and instead start it after the end of the array". This issue arrives because doing .slice(0) returns the full array. Thus, in order to simulate our intended result (an empty array) we just pass in an index that'll always return an empty result (this.length).
This is one thing that I really like about JavaScript: You can write a three line function for a trivial operation and still need 800 words to explain its true nature. Comments and feedback are appreciated.