How to Boost JavaScript Performance via Mutation and Pure Functions
Boost your app performance by better understanding Mutation and Pure Functions in JS
Immutability, Pure Functions, Side Effects, State Mutation are words we come across almost every day, but little do we know how they work or what they are or what goodies they bring to software development.
In this article, we look into all these to really know what they are and how to utilize them to boost the performance of our web apps.
Tip: Share and reuse JS Modules with bit.dev
Use Bit to encapsulate modules/components with all their dependencies and setup. Share them in Bit’s cloud, collaborate with your team and use them anywhere.
JavaScript: Primitives/References
We will start by knowing how JS manipulate and access our data types.
In JS, there are primitive data types and reference data types. primitive data types are referenced by value while the non-primitive/reference data types point to memory addresses.
Primitive data types are:
- Boolean
- Number
- String
- Null
- Undefined
- Symbol
Reference data types are:
- Objects
- Arrays
When we write a primitive data type like this:
let one = 1;
On the call stack, the one
variable directly points to the value 1
:
Call Stack
#000 one -> | 1 |
#001 | |
#002 | |
#003 | |
And if we change the value of the one variable
let one = 1
one = 3
The one
memory address #000 which holds the value 1
is directly changed to 3
.
But, if we write a reference data type like this:
let arr = {
one: 1
}
OR
let arr = new Object()
arr.one = 1
JS will create the object in the Heap memory and store the memory address of the object in arr
call stack location:
Call Stack Heap
#000 arr -> | #101 | #101 | one: 1 |
#001 | | #102 | |
#002 | | #103 | |
#003 | | #104 | |
You see arr
doesn't store the object directly but points to the memory location(#101) of the object. Unlike primitive data types that hold its value directly.
let arr = { one: 1 }
// arr holds the memory location of the object {one: 1}
// `arr` == #101let one = 1;
// `one` a primitive data type holds the value `1`
// one == 1
If we change the property in arr
like this:
arr.one = 2
We are basically telling the program to change the property value of the object pointed to by arr
. If you are good with pointers and references from C/C++ world, all these will be much clear to you.
When you pass a reference data type around, you are merely passing the address of its memory location, not the actual value.
function chg(arg) {
//arg points to the memory address of { one: 1 }
arg.one = 99
// This modification will affect { one: 1 } because arg points to its memory address, 101
}let arr = { one: 1 }
// address of `arr` is `#000`
// `arr` contains `#101`, adrress of object, `{one: 1}` in Heaplog(arr); // { one: 1 }chg(arr /* #101 */)
// #101 is passed inlog(arr) // { one: 99 }
// The change affected `arr`
Because reference data types hold memory address any change to it affects the memory it points to.
If we passed in a primitive data type:
function chg(arg) {
arg++
}let one = 1; // primitive data types holds the actual value of the variable.log(one) // 1chg(one /* 1 */)
// the value of `one` is passed in.log(one) // one is still `1`. No change because primitives only hold the value
State Mutation, Immutability
In Biology, we learned about DNA and DNA mutation. DNA has four base pairs: ATGC These pairs encode information to produce a type of protein in the body.
ATATGCATGCGATA
||||||||||||||
TACGAGCTAGGCTA|
|
v
AProteinaseInformation to produce a protein (eg, insulin etc)
This above DNA strand encodes information to produce the AProteinase protein useful for bone structure alignment.
If we change the DNA strand pairing, even by one one pair:
ATATGCATGCGATA
||||||||||||||
TACGAGCTAGGCTA|
v GTATGCATGCGATA
||||||||||||||
TACGAGCTAGGCTA
The DNA will produce a different protein because the information to produce the protein AProteinase has been tampered with. So another protein is produced which might be benign or in some cases, toxic.
GTATGCATGCGATA
||||||||||||||
TACGAGCTAGGCTA|
|
VNow produces _AProtienase
We call this change mutation or DNA mutation.
The mutation caused the change in the state of the DNA.
Now, coming to JS, the reference data types(Arrays, Objects) are data structures. These data structures hold the information to manipulate our application.
let state = {
wardens: 900,
animals: 800
}
This above Object holds the information of a Zoo application. If we change the number of animals in the state Object:
let state = {
wardens: 900,
animals: 800
}
state.animals = 90
Our state object will hold/encode a new information:
state = {
wardens: 900,
animals: 90
}
This is called mutation.
Our state was changed from:
state = {
wardens: 900,
animals: 800
}
to
state = {
wardens: 900,
animals: 90
}
Immutability comes when we want to preserve our state. To keep our state from changing we have to create a new instance of our state objects.
function bad(state) {
state.prp = 'yes'
return state
}function good(state) {
let newState = { ...state }
newState.prp = 'yes'
return newState
}
Immutability makes our app state predictable, ups the performance rate of our apps and to easily track changes in state.
Pure Functions, Side Effects
Pure functions are functions that accept an input and returns a value without modifying any data outside its scope(Side Effects). Its output or return value must depend on the input/arguments and pure functions must return a value.
function impure(arg) {
finalR.s = 90
return arg * finalR.s
}
The above function is not a pure function because it modified a state finalR.s
outside its scope.
function impure(arg) {
let f = finalR.s * arg
}
The above function also isn’t a pure function because it didn’t return a value though it didn’t modify any external state.
function impure(arg) {
return finalR.s * 3
}
The above function is impure, though it didn’t affect any external state, its output return finalR.s * 3
isn't dependent on the input arg
. Not only must pure function return a value but it must depend on the input.
function pure(arg) {
return arg * 4
}
Here is a pure function. It didn’t side effect
any external state and it returns an output based on the input.
Benefits
Personally, the only understandable goodie I find in these terms is mutation tracking
.
To know when to render your state is a very important thing. Many js frameworks have devised awesome and brilliant means to detect when to render their states. But at the center of it comes when to know re-render
after the initial rendering. This is called mutation tracking, to know when the state is mutated or changed so as to re-render the changed state.
Since we have implemented the immutability, we are sure our app state won’t be mutated anywhere in the app and also pure functions following its rules and principles. These make it easy to see if anything has changed.
let state = {
add: 0,
}funtion render() {
//...
}function effects(state,action) {
if(action == 'addTen') {
return {...state, add: state.add + 10}
}
return state;
}function shouldUpdate(s) {
if(s === state){
return false
}
return true
}state = effects(state, 'addTen')if(shouldUpdate(state)) {
render();
}
We have a small app here. The state is held by the state
object which has only add property. This app renders the app property. It shouldn't always render the state when anything happens but should check whether a change occurred in the state object.
Like so, we have an effects function, a pure function which we use to affect our state. You see that it returns a new state when the state is to be changed and returns the same state when no modification is required.
Therefore, we have a shouldUpdate function which checks using the ===
operator whether the old state and the new state is the same. If they are not the same the render function is called, to update the new state.
Conclusion
We looked into most common terms in web development and demonstrated what they are and how useful they are. This will be very much rewarding if you put it into practice.
If you have any question regarding this or anything I should add, correct or remove, feel free to comment, email or DM me. Cheers 🙏