Introduction
This is a series on sparsely used iteration methods in JavaScript. In this series, we cover several methods that are used to iterate over a collection of data, act on them or produce side effects. These operations, especially with arrays and strings, are very common in front end development with libraries like React and Angular.
Steps we'll cover:
What is Iteration?
Iteration is the process of looping through a collection of data and acting on each item in order to manipulate the item or create a side effect from it. Common examples of iteration methods in JavaScript are Array.prototype.forEach()
, Array.prototype.map()
and Array.prototype.reduce()
.
There are other iteration methods which are not as often used, but when needed are always handy tools for iterating over arrays, strings and objects. slice()
, some()
and every()
are such examples.
Some iteration methods, like Array.prototype.forEach()
iterates over all items in a collection and some like Array.prototype.slice()
iterates over the array partially.
We will cover several of these methods in this series, each in their own post.
In this article, we look into the details of using JavaScript slice which is available with identical implementations in Array
and String
prototypes. We'll get into their nuances with examples of both of these types.
Let's start with the array version of Javascript slice()
.
JS slice - Array.prototype.slice()
Array.prototype.slice()
is an Array
method that is used to extract a contiguous portion from an existing array. It can take two arguments: the start
and the end
indicator of the slice -- both of which are optional:
// Method signature
slice();
slice(start);
slice(start, end);
In this section, we set off with an example that expounds some general cases for selecting and storing a section from a source array. Then we'll see a couple of more interesting examples that help us generate arrays from arguments passed to a function using Array.prototype.call()
and Array.prototype.bind()
.
Slicing an Array
A typical examples of slicing an array involves extracting a contiguous part from an existing array. Such as the first three items, last three items and some items spanning from a certain index up until another index, as shown in the examples below:
const elements = ['Please', 'Send', 'Cats', 'Monkeys', 'And', 'Zebras', 'In', 'Large', 'Cages', 'Make', 'Sure', 'Padlocked'];
const firstThree = elements.slice(0, 3);
const lastThree = elements.slice(-3, elements.length);
const fromThirdToFifth = elements.slice(2, 5);
Both arguments of slice()
stand for zero-based index numbers or negative offset values. The first one (0
in the firstThree
assignment above) represents the starting index or offset in the source array where slicing should begin and the second one (3
) is the index or offset before which extraction should stop.
If we log the extracted values above, we can see the three elements we want from each slice:
console.log(firstThree); // ["Please", "Send", "Cats"]
console.log(lastThree); // ["Make", "Sure", "Padlocked"]
console.log(fromThirdToFifth); // ["Cats", "Monkeys", "And"]
It is important to notice that the item represented by the second argument is excluded from the extracted part. And we should be careful that unlike in Array.prototype.splice()
, whose second argument is the count of items to be deleted, the second argument of Array.prototype.slice()
does not stand for the number of items to be extracted, rather the position before which slicing should stop.
Other Nuances
No Arguments
If we don't pass in any argument, we just get a shallow copy of the source array with all items:
const allCopied = elements.slice();
console.log(allCopied);
// (12) ["Please", "Send", "Cats", "Monkeys", "And", "Zebras", "In", "Large", "Cages", "Make", "Sure", "Padlocked"]
It's effectively [...elements]
.
No Second Argument
If we don't pass in the second argument, the JavaScript slice extends to the last element:
const fromThird = elements.slice(2);
const lastThree = elements.slice(-3, elements.length);
const lastThreeWithNoSecArg = elements.slice(-3);
console.log(fromThird);
// (10) ["Cats", "Monkeys", "And", "Zebras", "In", "Large", "Cages", "Make", "Sure", "Padlocked"]
console.log(lastThree); // (3) ["Make", "Sure", "Padlocked"]
console.log(lastThreeWithNoSecArg); // (3) ["Make", "Sure", "Padlocked"]
Do notice that lastThreeWithNoSecArg
evaluates to the same portion as lastThree
, albeit missing the second argument.
Negative Offsets
As we've already seen above, we can pass in as arguments negative numbers that denote offset positions counted backwards from the last item. We can do this for both arguments:
const latestTwoBeforeLast = elements.slice(-3, -1);
console.log(latestTwoBeforeLast); // (2) ["Make", "Sure"]
Here, with latestTwoBeforeLast
, we're getting the latest two items without the final one.
Starting Position Greater Than Ending Position
If we pass in a greater value for start
than end
, we get an empty array:
const somewhereWeDontKnow = elements.slice(5, 2);
console.log(somewhereWeDontKnow); // []
Starting Position Greater Than Length of Array
Likewise, if we pass in a greater value for start
than array's length
, we get an empty array:
const somewhereInOuterSpace = elements.slice(15, 2);
console.log(somewhereInOuterSpace); // []
Sparse Arrays
If we our target portion has sparse items, they are also copied over:
const elements = ['Please', 'Send', , 'Cats', , 'Monkeys', 'And', 'Zebras', 'In', 'Large', 'Cages', 'Make', 'Sure', 'Padlocked'];
const sparseItems = elements.slice(0, 6);
console.log(sparseItems);
// (6) [ 'Please', 'Send', <1 empty item>, 'Cats', <1 empty item>, 'Monkeys' ]
Creating Arrays from a List of Arguments
Now we can go a bit crazy about slicing. Let's construct an array from a list of arguments passed to a function:
const createArray = (...args) => Array.prototype.slice.call(args);
const array = createArray(1, 2, 3, 4);
console.log(array); // (4) [1, 2, 3, 4]
Here, we received args
as a list first, but converted it to an array with rest params ...args
. We then bound the array to Array.prototype.slice()
with Function.prototype.call()
.
That was easy.
The next level way of doing this is in the messiest possible way:
const boundSlice = Function.prototype.call.bind(Array.prototype.slice);
const createArray = (...args) => boundSlice(args);
const array = createArray(1, 2, 3, 4);
console.log(array); // (4) [1, 2, 3, 4]
It seems like a overhead, but what were are doing is declaring two helper functions.
The first one, boundSlice
, is derived from binding the Function.prototype.call()
method with Array.prototype.slice()
which is an array function object. So, we are getting a copy of Function.prototype.call()
bound to Array.prototype.slice()
and storing it in boundSlice
.
This basically means, if we invoke boundSlice()
, we are in effect invoking Array.prototype.slice.call()
. If we then pass in an argument list as rest params to boundSlice()
, Array.prototype.slice()
is invoked on this array and a copy of the array is created from it.
In createArray()
, we are accumulating the arguments and passing them to boundSlice()
as args
. So, whatever we pass to createArray()
is returned as items inside an array.
JS slice - String.prototype.slice()
Now, let's consider the JavaScript slice()
method for strings.
String.prototype.slice()
does the exact same thing as Array.prototype.slice()
, but with characters in a string.
Like Array.prototype.slice()
, it takes two optional arguments, start
and end
:
// Method signature
slice();
slice(start);
slice(end);
Slicing a String
String.prototype.slice()
is useful for directly working on strings. It removes the hassle of converting a string to an array with Array.prototype.split()
:
const mnemonic = 'Please Send Cats Monkeys And Zebras In Large Cages Make Sure Padlocked';
const firstThreeChars = mnemonic.slice(0, 3);
const lastThreeChars = mnemonic.slice(-3, mnemonic.length);
const fromThirdToFifthChars = mnemonic.slice(2, 5);
console.log(firstThreeChars); // "Ple"
console.log(lastThreeChars); // "ked"
console.log(fromThirdToFifthChars); // "eas"
Again, both arguments represent zero-based index numbers or negative offset values. Here, the first one, 0
in the firstThree
assignment, stands for the starting index or offset of the portion and the second one (3
) for the index or offset before which extraction should stop.
With No Arguments
If we don't pass in any arguments, we get a new copy of the whole string:
const mnemonic = 'Please Send Cats Monkeys And Zebras In Large Cages Make Sure Padlocked';
const memorizedMnemonic = mnemonic.slice();
console.log(memorizedMnemonic);
// "Please Send Cats Monkeys And Zebras In Large Cages Make Sure Padlocked"
With No Second Argument
If we don't pass the second argument, the length
of the string becomes end
:
const charsFromThird = mnemonic.slice(2);
console.log(charsFromThird);
// "ease Send Cats Monkeys And Zebras In Large Cages Make Sure Padlocked"
Negative Offsets
Similar to Array.prototype.slice()
, negative values for start
and end
represent offset positions from the end of the array:
const lastThreeChars = mnemonic.slice(-3);
const latestTwoCharsBeforeLast = mnemonic.slice(-3, -1);
console.log(lastThreeChars); // "ked"
console.log(latestTwoCharsBeforeLast); // "ke"
Starting Position Greater Than Ending Position or Length of Array
Again, similar to Array.prototype.slice()
, if start
is greater than end
or length
of string, we get an empty string:
const inAnotherDimension = mnemonic.slice(5, 2);
const doingStringTheory = mnemonic.slice(15, 2);
console.log(inAnotherDimension); // ""
console.log(doingStringTheory); // ""
Conclusion
In this post, we expounded the slice()
method in JavaScript. We saw that JavaScript implements slice()
in two flavors: one for Array
s with Array.prototype.slice()
and one for String
s with String.prototype.slice()
. We found out through examples that both methods are used to extract contiguous portions from a source object representing these types.
We also saw that String.prototype.slice()
is identical implementation of Array.prototype.slice()
that removes the overhead of converting a string to an array of characters.
We also covered how function composition and context binding with Function.prototype.call()
and Function.prototype.bind()
allows us to define custom functions that help us generate arrays from a list of arguments.