Home >Web Front-end >JS Tutorial >Immutable Array Methods: Write Cleaner JavaScript Code
This article follows the JavaScript variable assignment and mutation guide, and discusses the problems and solutions brought by the mutated array method. Many JavaScript array methods directly modify the original array, which can easily lead to difficult-to-trace bugs in large applications. But not all methods are like this. Methods such as Array.prototype.slice()
, Array.prototype.concat()
, Array.prototype.map()
, and Array.prototype.filter()
will return a new array, and the original array remains the same. To avoid unexpected mutations, we can write our own immutable array methods that return new array objects instead of modifying the original array. This article will cover immutable versions of methods such as pop
, push
, shift
, unshift
, reverse
, splice
, and
Array mutation in JavaScript
JavaScript arrays are objects and can therefore be modified. Many built-in array methods directly modify the array itself, which violates best practices in programming. The following example shows a potential problem:
<code class="language-javascript">const numbers = [1,2,3]; const countdown = numbers.reverse();</code>
numbers
This code seems normal. We create an array called countdown
and want to create an array called numbers
containing numbers arranged in reverse order in the array. On the surface, it does work:
<code class="language-javascript">countdown // [3, 2, 1]</code>
But the reverse()
method also modified the numbers
array, which is not the result we want:
<code class="language-javascript">numbers // [3, 2, 1]</code>
What's worse is that both variables refer to the same array, so any subsequent changes to one of the arrays will affect the other. If we use the Array.prototype.push()
method to add an element with a value of 0 to the end of the countdown
array, the numbers
array will also change the same way (because they all refer to the same array):
<code class="language-javascript">countdown.push(0) countdown // [3, 2, 1, 0] numbers // [3, 2, 1, 0]</code>
This side effect is easily overlooked in large applications and leads to difficult-to-track bugs.
Variable array method in JavaScript
In addition to reverse()
, there are many other array methods that cause this mutation:
Array.prototype.pop()
Array.prototype.push()
Array.prototype.shift()
Array.prototype.unshift()
Array.prototype.reverse()
Array.prototype.sort()
Array.prototype.splice()
In contrast, some methods do not modify the original array, but return a new array:
Array.prototype.slice()
Array.prototype.concat()
Array.prototype.map()
Array.prototype.filter()
These methods return a new array based on the actions performed. For example, the map()
method can be used to double all numbers in an array:
<code class="language-javascript">const numbers = [1,2,3]; const countdown = numbers.reverse();</code>
Now, if we check the numbers
array, we can see that it is not affected by the method call:
<code class="language-javascript">countdown // [3, 2, 1]</code>
Why do some methods modify arrays, while others don't? There seems to be no clear reason for this, but the recently added methods tend to make it immutable. It may be difficult to remember which methods will modify the array and which methods will not modify the array.
Immutable array method: Fix mutation problem
We have determined that mutations can cause problems, and that many array methods will cause mutations. Let's see how to avoid using them. It is not difficult to write some functions that return new array objects instead of modifying the original array. These functions are our immutable array methods.
Because we won't modify Array.prototype
, these functions always take the array itself as the first parameter.
pop
Let's start by writing a new pop
function that returns a copy of the original array but does not contain the last item. Note that Array.prototype.pop()
returns the value popped from the end of the array:
<code class="language-javascript">numbers // [3, 2, 1]</code>
This function uses Array.prototype.slice()
to return a copy of the array, but removes the last item. The second parameter -1 means "stop slice, 1 position before the end". We can see how it works in the following example:
<code class="language-javascript">countdown.push(0) countdown // [3, 2, 1, 0] numbers // [3, 2, 1, 0]</code>
push
Next, let's create a push()
function that will return a new array, but append a new element at the end:
<code class="language-javascript">const numbers = [1,2,3]; const evens = numbers.map(number => number * 2);</code>
This creates a copy of the array using the extension operator. It then adds the value provided as the second parameter to the end of the new array. Here is an example:
<code class="language-javascript">numbers // [1, 2, 3]</code>
shift
and unshift
We can write alternatives to Array.prototype.shift()
and Array.prototype.unshift()
similarly:
<code class="language-javascript">const pop = array => array.slice(0,-1);</code>
For our shift()
function, we just cut the first element from the array instead of the last element. This can be seen in the following example:
<code class="language-javascript">const food = ['?','?','?','?']; pop(food) // ['?','?','?']</code>
Our unshift()
method will return a new array and append a new value to the beginning of the array:
<code class="language-javascript">const push = (array, value) => [...array,value];</code>The
Extended operator allows us to place values in an array in any order. We just have to put the new value in front of the original copy of the array. We can see how it works in the following example:
<code class="language-javascript">const food = ['?','?','?','?']; push(food,'?') // ['?','?','?','?','?']</code>
reverse
Now let's try to write an alternative to the Array.prototype.reverse()
method. It will return a copy of the array arranged in reverse order, rather than modifying the original array:
<code class="language-javascript">const numbers = [1,2,3]; const countdown = numbers.reverse();</code>
This method still uses the Array.prototype.reverse()
method, but applies to a copy of the original array we create using the extension operator. There is no problem modifying the object right after it is created, and that's what we do here. We can see how it works in the following example:
<code class="language-javascript">countdown // [3, 2, 1]</code>
splice
Lastly, let's deal with Array.prototype.splice()
. This is a very general function, so we won't completely rewrite its functionality (although it's an interesting exercise). Instead, we will focus on two main uses of slice
: deleting items from arrays and inserting items into arrays.
Delete array items
Let's start with a function that will return a new array, but delete an item at the given index:
<code class="language-javascript">numbers // [3, 2, 1]</code>
This uses Array.prototype.slice()
to cut the array in half - both sides of the item we want to delete. The first slice returns a new array, copying elements of the original array until the index specified as the parameter's index. The second slice returns an array containing the elements we want to delete, all the way to the end of the original array. We then use the extension operator to put them all in a new array. We can check if this works by trying to delete the item with index 2 in the food
array below:
<code class="language-javascript">countdown.push(0) countdown // [3, 2, 1, 0] numbers // [3, 2, 1, 0]</code>
Add array items
Finally, let's write a function that will return a new array and insert a new value at a specific index:
<code class="language-javascript">const numbers = [1,2,3]; const evens = numbers.map(number => number * 2);</code>
This is similar to the way the remove()
function works. It creates two slices of the array, but this time includes the elements at the index provided. When we regroup the two slices together, we insert the value provided as the parameter between them. We can check if this works by trying to insert a cupcake emoji into the middle of our food
array:
<code class="language-javascript">numbers // [1, 2, 3]</code>
Now we have an immutable set of array methods that do not modify the original array. You can save them in one place and use them in your project. You can namespace them by taking them as a single object method, or use them as is when needed. These methods should be sufficient for most array operations. If you need to do different things, remember the golden rule: first use the extension operator to create a copy of the original array. Then, immediately apply any variable method to this copy.
Conclusion
In this article, we examine how JavaScript makes life difficult by modifying an array method that modifies the original array as part of the language. We then wrote our own immutable array method to replace these functions.
What other array methods can you think of that can benefit from having an immutable version?
FAQs on Creating and Using Immutable Array Methods in JavaScript
Invariance is a basic concept in programming, which refers to the state of an object that cannot be modified after it is created. In JavaScript, invariance is not enforced by default. However, it is a powerful concept that can help you write more predictable and easier to maintain code. It is especially useful in functional programming, where invariance prevents errors and complexity caused by changing states.
Using immutable array methods in JavaScript can write clearer and easier to maintain code. Since these methods do not modify the original array, they can prevent side effects that may cause errors. This is especially important in large code bases or when dealing with complex data structures. The immutable method returns a new array, while the original array remains unchanged. This makes your code more predictable and easier to debug.
JavaScript provides some immutable array methods that do not change the original array. Some examples include map()
, filter()
, reduce()
, and concat()
methods. The map()
method creates a new array containing the results of the function provided for each element in the array. The filter()
method creates a new array containing all elements of the test implemented by the provided function. The reduce()
method applies a function to each element in the accumulator and array to reduce it to a single output value. The concat()
method is used to merge two or more arrays and return a new array.
JavaScript does not provide a built-in method to make arrays immutable. However, you can achieve invariance by using methods that do not change the original array (e.g. map()
, filter()
, reduce()
, and concat()
). Another approach is to use the Object.freeze()
method, which prevents the addition of new properties to the object, prevents the removal of existing properties, and prevents the enumeration, configurability, or writability of existing properties.
The main difference between mutable and immutable methods in JavaScript is how they handle the original array. A mutable method modifies the original array, while an immutable method does not. Instead, the immutable method returns a new array. This makes your code more predictable and easier to debug as it prevents side effects that can cause errors.
Yes, you can use immutable arrays with other data types in JavaScript. The concept of invariance applies to all data types, not just arrays. For example, you can use immutable methods with strings, numbers, objects, etc. This can help you write clearer and easier to maintain code.
Using immutable array methods may have some performance impact, as they usually create a new array instead of modifying the original array. This can lead to increased memory usage, especially in large arrays. However, in most cases, the benefits of using immutable methods, such as clearer code and fewer bugs, outweigh the potential performance costs.
reduce()
method in JavaScript? The reduce()
method in JavaScript is an immutable method that applies a function to each element in the accumulator and array to reduce it to a single output value. Here is an example of how to use it:
<code class="language-javascript">const numbers = [1,2,3]; const countdown = numbers.reverse();</code>
In this example, the reduce()
method calculates the sum of all elements in the array.
concat()
method in JavaScript? The concat()
method in JavaScript is used to merge two or more arrays. This method does not change the existing array, but returns a new array. Here is an example:
<code class="language-javascript">countdown // [3, 2, 1]</code>
In this example, the concat()
method merges array1
and array2
into a new array array3
.
filter()
method in JavaScript? The filter()
method in JavaScript creates a new array containing all elements of the test implemented by the provided function. Here is an example of how to use it:
<code class="language-javascript">numbers // [3, 2, 1]</code>
In this example, the filter()
method creates a new array containing numbers greater than 3.
The above is the detailed content of Immutable Array Methods: Write Cleaner JavaScript Code. For more information, please follow other related articles on the PHP Chinese website!