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 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:
let numstr = 5..toString();
console.log(numstr) //Returns: 5
Notice that calling .toString() method on 5 returns 5. Let's look at another example
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,
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,
myVar = "something"
This is allowed in javascript, because javascript is not static typed and also javascript hoists variables.
You can also assign a variable by referencing another variable. For example,
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,
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,
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 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
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,
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.
let str = "hello";
str = "Hey"
console.log(str); //Returns Hey