Understanding the slice method in javascript: the basics, negative indexing and the concept of shallow copy
Sandesh Yadav
Posted on June 7, 2020
This article is for you:
- If you are an absolute beginner in JS.
- If you have copied and pasted a chunk of code from stackoverflow which had
slice()
method but didn't understand the code completely. - If you have used it earlier and have been planning to get a deeper understanding of it.
- And its definitely for you if you thought there couldn't be an 2500+ words article merely on slice() method.
The name suggests it clearly. All that slice()
method does is get us a slice(a portion) of things. In this article we will discuss what are those things, how do we slice them and a few other aspects of the slice()
method. First question:
What are the things we can get a slice of?
There are two types of things(more appropriately called objects
) which we can get a slice of. In technical terms, the slice()
method can be applied to two types of objects in Javascript: Strings and Arrays.
String samples
//Examples of String Objects in Javascript
let str_greet = new String("Hey! Developers");
let str_numbers = new String("987654321");
let str_spcl_chars = new String("@@###!!");
let str_zeros = new String("000000");
//The following are 'strings' but not 'String objects'
//However the 'slice()' method can be applied to them too
let greet = "Hello World";
let num_str = "12345";
Array samples
//Examples of Arrays in Javascript
let fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let even_arr = [2, 4, 6, 8, 10];
let str_numb_arr = ["4", "10", "40", "5"];
let mixed_arr = ["John", "Doe", 32, "1988"];
To get into details of what Strings and Arrays are in Javascript is beyond the scope of this article. The method's behavior is almost same in both the cases. It will either return a sub-string or a sub-array. Just note that most of the discussion and examples are going to be about Array.Slice()
method. Let's get started.
The Basics
The Slice method returns a portion of an array into a new array. Which portion it returns is decided by two optional parameters begin and end.
Syntax
arr.slice([begin[, end]])
let fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fruits.slice(2,4);
console.log(my_fav_fruits);
// output -> [ 'banana', 'grapes' ]
console.log(fruits);
// output -> [ 'apple', 'mango', 'banana', 'grapes', 'blueberry', 'kiwi', 'papaya' ]
Now that we have seen a basic example, let's discuss some of the facts related to the method.
Slice Fact 1: The original array is not modified. The newly formed array assigned to my_fav_fruits
variable is just a copy of a portion of the original array fruits
. The original array remains intact. The new array is often referred as a shallow copy of the original array which we will discuss later in the article.
Slice Fact 2: The item at the end position is not included in the new array. As we can see in the example below, the item at the 5th position (Kiwi) is not included in the output array(my_fav_fruits
).
let fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fruits.slice(1,5);
console.log(my_fav_fruits);
// output -> [ 'mango', 'banana', 'grapes', 'blueberry' ]
Slice Fact 3: If end is not provided, then it assumes the end parameter to be 'the actual end of the array', which is equivalent to the length
of the array(fruits.length
).
let fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fruits.slice(2);
console.log(my_fav_fruits);
// output -> [ 'banana', 'grapes', 'blueberry', 'kiwi', 'papaya' ]
Slice Fact 4: If the second parameter (end) is a number higher than the length
of the array, then the resulting array is exactly the same as in Slice Fact 3. It returns the elements through 'the actual end of the array', which is equivalent to the length
of the array(fruits.length
).
let fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fruits.slice(2,100);
console.log(my_fav_fruits);
// output -> [ 'banana', 'grapes', 'blueberry', 'kiwi', 'papaya' ]
Slice Fact 5: Since both the parameters are optional, its perfectly valid to call the method with no parameters at all. In such case it returns the exact copy of the original array. This feature is sometimes used to get a copy of an array in Javascript.
let fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fruits.slice();
console.log(my_fav_fruits);
// output ->[ 'apple', 'mango', 'banana', 'grapes', 'blueberry', 'kiwi', 'papaya' ]
Slice Fact 6: For the first parameter(begin), undefined
value is accepted and considered as 0. The returned array has elements from the starting position.
let fav_fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fav_fruits.slice(undefined, 5);
console.log(my_fav_fruits);
//output -> [ 'apple', 'mango', 'banana', 'grapes', 'blueberry' ]
Slice Fact 7: If the first parameter(begin) is greater than or equal to the length of the array, then an empty array
will be returned.
let fav_fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fav_fruits.slice(100, 5);
console.log(my_fav_fruits);
//output -> []
Negative Indexing
The slice method supports the negative indexing. To understand this, let's look at the image below. The items at the last position are indexed as -1
and the one at second to last position as -2
and so on. While the positive indexing moves from left to right, the negative one moves from right to left. With this kind of indexing, the index of first element is 'negative value of the length of the array'.
Let's see a few examples of how slice()
works with the negative indexing. To continue the Slice Facts list, let's add the eighth one:
Slice Fact 8: The method works perfectly fine with negative indexes. The working mechanism is same as we saw earlier. Only the indexing changes.
let fav_fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fav_fruits.slice(-5, -1);
console.log(my_fav_fruits);
//output -> [ 'banana', 'grapes', 'blueberry', 'kiwi' ]
Slice Fact 9: Utilizing the negative indexing property, you can get the last 'x number of elements' from an array calling slice(-x)
.
In the example below, we are fetching the 'last 4 elements' of the fruits array. We put the begin(first parameter) as -4
and omit the end(second parameter). The logic behind this is very simple. The items from the position -4
to the end(-4, -3, -2 and -1)
are being returned.
let fav_fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fav_fruits.slice(-4);
console.log(my_fav_fruits);
//output -> [ 'grapes', 'blueberry', 'kiwi', 'papaya' ]
Slice Fact 10: The mix of negative and positive index works perfectly fine. However one needs to be careful while doing so as it can be a bit confusing. The thumb rule in most cases is: You will just have to make sure the position of the first parameter is on the left side of the second parameter. Otherwise, you will get an empty array. While this may look a bit confusing initially, if you look at the indexes closely, it becomes very simple.
let fav_fruits = ["apple", "mango", "banana", "grapes", "blueberry", "kiwi", "papaya"];
let my_fav_fruits = fav_fruits.slice(-6, 5);
console.log(my_fav_fruits);
//output -> [ 'mango', 'banana', 'grapes', 'blueberry' ]
Shallow Copy
As mentioned earlier in the article, let's discuss what shallow copy means. This will help us determine when to use slice()
, when to avoid it and when to be extra cautious while using it. But before getting into it, I will write a quick summary of primitive and non-primitive data types in javascript. This is important to understand the concept of 'shallow copy' that the slice()
method adopts while creating a copy of an array.
So far, we have seen arrays in our examples where elements are plain strings and numbers. Instead of calling them plain, programming world has a special term for them called primitive. To be honest, the detailed discussion of primitive and non-primitive will take another 30 minutes or more. I will keep short and simple by putting only relevant items in the list here.
Primitive Values
- numbers
- strings
- boolean
Non Primitive Values
- Objects
- Arrays (which is actually a special kind of object)
The way primitive data is stored in computer's memory is different from the way non-primitives are stored. Primitives are stored by values whereas non-primitives are stored by references. Let's see what that means with examples.
//primitive values
let a = 5;
let grt_str = "Hello World";
let bool_val = 0;
When I execute the above lines, JS will tell computer:
- Hey computer, I have this variable named '
a
' and remember its value is 5. - Hey computer, I have this variable named '
grt_str
' and remember its value is "Hello World". - Hey computer, I have this variable named '
bool_val
' and remember its value is 0.
// Non Primitive Values
let fox_arr = ["JS", "Python", "PHP"];
let fox_obj = {
'name': 'FoxBits',
'type': 'web',
'age' : 2
};
Here, when I execute the above lines, JS will tell computer:
- Hey computer, I have this array named '
fox_arr
'. Save it in your memory and tell me the address of the memory block where you stored it. - Hey computer, I have this object named '
fox_obj
'. Save it in your memory and tell me the address of the memory block where you stored it.
I hope this gave some idea on how primitives and no primitives are stored differently in javascript. All these will make sense soon, I promise. We will now create an array which will contain all of the above declared values: both primitive and non-primitive ones.
//this array has 3 primitive and 2 non primitive values
let mixed_array = [a, grt_str, bool_val, fox_arr, fox_obj]
I want to show you a roughly designed graphical representation of how this array(mixed_array
) will be stored in memory. Consider the yellow boxes as memory blocks.
As you can see in the image above, for the primitive data_(a, grt_str and bool_val)_ the values are stored directly in the memory blocks. Whereas for the non-primitive ones(fox_arr and fox_obj), the data is stored in two layers. In the first layer, the memory reference to fox_arr and fox_obj are stored. In the second layer, the actual array and object items are stored.
Now, lets get back to the concept of 'shallow copy'. When we say the slice()
method creates a shallow copy, it implies that only the first layer is copied into the new array. This means that for the ones in the first layer, the primitive values, a new copy with values is created for each item. But for the items in the second layer, only the memory references are copied. Suppose I execute the following script:
// Using slice without parameters
// will create a shallow copy of all the elements in original array
let new_mixed_array = mixed_array.slice();
Then the storage of new_mixed_array
, a copy of mixed_array
in memory blocks will look something like this:
What we can infer from the image above is:
1. In the newly created copy of mixed_array
, the primitive values are copied as values. This means that if we alter these values in the new array, the corresponding values in the original array will not change. And vice versa.
2. In this copy, the non-primitive values are copied as reference. They are still referencing to the objects in the original _mixed_array_
. This means that if we make any change in these items in the new array then the original values will also change. And vice versa.
Let's try out the same in code below. I will copy the relevant code from the above code blocks and make some changes to the new_mixed_array
and then check the original array.
//primitive values
let a = 5;
let grt_str = "Hello World";
let bool_val = 0;
// Non Primitive Values
let fox_arr = ["JS", "Python", "PHP"];
let fox_obj = {
'name': 'FoxBits',
'type': 'web',
'age' : 2
};
//this array has 3 primitive and 2 non primitive values
let mixed_array = [a, grt_str, bool_val, fox_arr, fox_obj]
console.log(mixed_array);
This is the output of console.log()
before using any method.
// Using slice without parameters
// will create a shallow copy of all the elements in original array
let new_mixed_array = mixed_array.slice();
// Changing the first item in the new_mixed_array
new_mixed_array[0] = '10'
// Logging the original array to check if anything has changed
console.log(mixed_array);
As expected, this will cause no change in the original array. The same output for the original array can be seen again. This is because we updated a primitive value.
Here comes the important step. Lets make some change to an array in the new_mixed_array
. We will target the third item of the fourth element which is 'PHP'. We will replace 'PHP' with 'Flutter' in the new array and then will log the original array in console.
// new_mixed_array[3] is an array with three elements
// the third element is 'PHP'
// we intend to replace it with 'Flutter'
new_mixed_array[3][2] = 'Flutter';
console.log(mixed_array);
Now that we have seen what shallow copy means, we have good reasons to be cautious while using the slice()
method if the array contains non-primitive element(s). There are times when developers use it and expect the new array to be independent of the original array, which may not always be the case.
Let's discuss the last topic and then we will wrap up.
The slice() method for Strings
So far we only saw arrays being sliced. But as mentioned in the beginning of the article, slice() is also available for Strings. The behavior for Strings is almost same as for Arrays. The first difference is that the items involved in the process are part of a string, not elements of array. And similarly the output is a string, not an array. To understand it just a single example for String slice will suffice.
let str_greet = new String("Hey! Developers");
let greet_sub = str_greet.slice(0, 4);
console.log(greet_sub);
Summary
Let's summarize the article.
Slice()
method is available for Strings and Arrays in Javascript.
It has two optional parameters which we can use in different combinations to get interesting results.
It does not make modifications to the original array/string.
We can use negative indexes with the slice()
method which makes it even more useful.
The method returns a shallow copy of the original array into a new array.
Thus, in the new array primitive values get a new copy whereas the non-primitives get only the copy of reference to original objects.
For non-primitive values, any changes done in the resulting array items will be reflected to the original array and vice-versa.
I hope this article helped. If you liked it I recommend another one related to javascript's push()
, pop()
, shift()
and unshift()
methods. It has graphics too which will help you understand the concepts easily.
The four common Javascript array methods Push, Pop, Shift and Unshift
This article was originally published in WebTechParadise
Posted on June 7, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.