Ultimate Guide to Mutations in Javascript.
Tags: webdev, javascript, programming
Published: 2023-03-26 13:31:32

Mutations in javascript although simple, can be very tricky to understand.

In this write-up, you'll learn to, assign values to a variable, make shallow copies in javascript, and also you'll understand how mutations work and how to avoid them.

To understand mutations, let's understand the different types of data types available and how variables are assigned and then take it from there. 

Without further ado let's dive in.

## Javascript data types.

Data types are the different types of values a [variable](#variables) can store. In javascript, there are two distinct categories of data types, primitive data types, and user-defined or object types.

### Primitive data types.

In javascript, primitive data types are implemented at the core of the language. Primitive values in javascript include:

- string. A sequence of characters. For example, "Hello world!", "Toby" etc.
- number. Smaller integer values including decimals. For example -0.3, 5, 10, etc.
- bigint. Larger integer values or numbers of great length
- Boolean. For example, true or false
- undefined. 
- symbol.
- null. 

In javascript, primitive values are immutable and do not have any methods although they behave as if they do.

let's understand it by looking at the code blocks below:

```js
let numstr = 5..toString(); 

console.log(numstr) //Returns: 5
```

Notice that calling `.toString()` method on 5 returns 5. Let's look at another example

```js
var num = 5; // assigning a primitive of type number to variable num.

let numstr = num.toString(); // converts "num" to string.

console.log(numstr) //Returns 5 

console.log(typeof numstr) //Returns string
```

In this example, you realize that calling `.toString()` on the variable also returns 5, what happens is that javascript coerces the variable by wrapping the variable in a wrapper object and then calls the `.toString` method on the wrapper object but not on the actual primitive value.

### Reference / User-defined data types.

User-defined types are data types that are constructed by an author using primitive values and/or other user-defined types. Javascript user-defined types are all objects by default. These include:

- Objects literals
- Arrays
- Functions. Functions are a special type of object. 
- Dates and any object defined using the new keyword.


## Variables.

A variable is a named storage location. Variables point to a location containing a specific data type. For example, a fridge containing a snack, the fridge in this context is your **"variable"** and the snack is the value of a specific data type.

### Assigning variables(primitive and reference types).

To assign a variable, you must declare it using any of these keywords `const`, `let`, and `var`. For example,

```js
const myVar = "something"

//or

let myVAr = "something"

//or 

var myVay = "something"
```
In these examples "something" can be any of the data types you know. It's also common to see variables defined without being declared. For example,

```js
myVar = "something"
```
This is allowed in javascript, because javascript is not [static typed](https://developer.mozilla.org/en-US/docs/Glossary/Static_typing#:~:text=A%20statically%2Dtyped%20language%20is,not%20indicate%20their%20variable%20types.) and also javascript [hoists](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting#:~:text=JavaScript%20Hoisting%20refers%20to%20the,defined%20in%20the%20ECMAScript%20specification.) variables.

You can also assign a variable by referencing another variable. For example,

```js
myVar = "something"

myVar1 = myVar // myVar1 is also equal to "something"
```
In javascript, assigning **primitive values** to variables like this does not mean **myVar1** and **myVar** are pointing to the same storage location, instead **myVar1** points to a different location containing the value it obtained from **myVar**. This means that changing the value in one variable does not affect the other.

However, assigning object types to variables behaves slightly differently.

## Shallow Copy

Let's look at an example,

```js
let obj1 = {name: "John", age: 10};

let obj2 = {name: "John", age: 10};
```
In this example, both **obj1** and **obj2** hold objects of similar values but are completely different, both objects point to different memory locations. Changing the values in **obj1** does not change that of **obj2**. Let's look at another example that uses a reference to pass value,

```js
let obj1 = {name: "John", age: 10};

let obj3 = obj1; // obj3 = {name: "John", age: 10};

let arr = [2, 4, 6, 8];

let arr2 = [...arr, 10] //arr2 = [2, 4, 6, 8, 10]
```
In this example **obj3** and **arr2** make a [shallow copy](https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy#:~:text=A%20shallow%20copy%20of%20an,which%20the%20copy%20was%20made.) of **obj1** and **arr** respectively, which means they're pointing to the same memory location, any selective change to either **name** or **age** properties of either object variable affects the value of the other object variable, and also changing the values in **arr** affects **arr2**.

**Note:** Passing javascript objects by reference, or using the spread operator creates a shallow copy of the source variable.

In the same context as the latter example let's look at this example 

```js
obj3.name = "Doe"; // obj3 becomes {name: "Doe", age: 10};

console.log(obj1) // Returns {name: "Doe", age: 10}
```
You realize that changing the value of the name property in **obj3** changes the value for **obj1.name**.

## Mutations.

Mutation means a change in the original code or data.

**Note:** Primitive values cannot be mutated. They're read-only. For example,

```js
let num = 5;

let str = "Hello";

num = 8;
console.log(num); //Returns 8 as the new value for num

str[0] = "p"

console.log(str);  //Returns Hello 
```

In the example, notice that it's possible to reassign a primitive value to a variable as in `line 3` but it's not possible to change the value **"h"** at `str[0]` to p. Let's look at another example involving strings.

```js
let str = "hello";

str = "Hey"

console.log(str); //Returns Hey
```

In this example, we reassigned the str variable a value of **"Hey"** and did not just replace a character in the old string.




###  Mutating object types.

**Note:** Using `const` to declare a variable means it cannot be reassigned to a new value for both primitive and object data types. However, for object types, their values can be mutated. For example,

```js
const obj = {
    name: "John",
    Subscribers:50
}

const arr = [{name:"Tutor", Subscribers: 400}, {name:"Doe", Subscribers: 10}]

obj = arr[0] // Returns TypeError: Assignment to constant variable.
```
Notice the error message says it's not possible to assign a new value. 

Now, let's look at an example of mutating a **const** variable,

```js
const obj = {
    name: "John",
    Subscribers:50
}

const obj2 = obj;

obj2.name = "Dave"

console.log(obj) //Returns { name: 'Dave', Subscribers: 50 }
```
Notice that the value of the name property in **obj** has changed or better has been mutated.  A metaphor for these examples will be, signing up for an account on Gmail with a name that's already been taken. It throws an error saying "Username already taken"(variable space already filled) but the owner of that specific account can change his username and it updates his details with the change.

Let's look at another example of mutation using the array from the previous example,

```js
const arr = [{name:"Tutor", Subscribers: 400}, {name:"Doe", Subscribers: 10}]

const newArr = [...arr] //making shallow copy

newArr[0].name = "Clement" 

console.log(newArr[0]) //Returns {name:"Clement", Subscribers: 400}

console.log(arr[0]) //Returns {name:"Clement", Subscribers: 400}

```
The example returns a new name for the first array object, you realize that although the name changed other values did stay the same and the object at that location remains the same object even if we selectively change the number of subscribers too.

Now that we've covered mutation let's look at the opposite or what's not considered a mutation.

Using the same example,

```js
const arr = [{name:"Tutor", Subscribers: 400}, {name:"Doe", Subscribers: 10}]

const newArr = [...arr]

newArr[0] = {name: "Clement", Subscribers: 0}

console.log(newArr) //Returns [{name: "Clement", Subscribers: 0}, { name: 'Doe', Subscribers: 10 }]

console.log(arr) //Returns arr unchanged

```
Notice the difference in these examples on `line 3`, In the latter example the position newArr[0] has been assigned a new object.  A better metaphor to understand these two examples will be, Taking your Jeep to a garage for painting. Although the Jeep comes out with a different color it remains the same old Jeep(mutating an object). However, a new Lambo entering the same garage after the Jeeps leaves represents a completely different vehicle in the garage(reassign an object).

**Note:** To avoid mutating variables mistakenly, always reassign values to shallow copies of object types 

### Wrapping Up.

In this article, we've covered data types, variables, how to assign and reassign variable values, how to make shallow copies of objects and arrays, how to mutate objects, and how to avoid mutating objects.

Did you enjoy this article? If so, get more similar content by following me. Thanks for reading. Happy coding.