JavaScript optimization tips
As a developer, we always look for ways to make our code faster and better.
But before that, writing high performance code requires three things:
- Know about the language and how it works
- Design based on the use case
- Debug! Fix! Repeat!
Remember this,
Any fool can write code that a computer can understand. Good programmers write code that humans can understand. - Martin Fowler
Let us see how to make JavaScript code to run even faster.
Be lazy
A lazy algorithm defers computation until it is necessary to execute and then produces a result.
const someFn = () => {
doSomeOperation()
return () => {
doExpensiveOperation()
}
}
const t = someArray.filter((x) => checkSomeCondition(x)).map((x) => someFn(x))
// Now execute the expensive operation only when needed.
t.map((x) => t())
TL;DR; The fastest code is the code that is not executed. So try to defer execution as much as possible.
Beware of object chaining
The JavaScript uses prototype inheritance. All the objects in the JavaScript world are instances of the Object.
The MDN says,
When trying to access a property of an object, the property will not only be sought on the object but on the prototype of the object, the prototype of the prototype, and so on until either a property with a matching name is found or the end of the prototype chain is reached.
For every property the JavaScript engine will have to go through the entire object chain until it finds a match. This is so resource intensive and hogs on your application performance if not used correctly.
So don't do this
const name = userResponse.data.user.firstname + userResponse.data.user.lastname
Instead do this
const user = userResponse.data.user
const name = user.firstname + user.lastname
TL;DR; Use temporary variable to hold the chained properties. Rather than repeatedly going through the chain.
Think before using transpilers
In the above case, the userResponse
may or may not have the data
object. That data
object may or may not have the user
property.
We can check that while getting the value like this
let name = ''
if (userResponse) {
const data = userResponse.data
if (data && data.user) {
const user = data.user
if (user.firstname) {
name += user.firstname
}
if (user.lastname) {
name += user.firstname
}
}
}
Well that is verbose. More the code, more the surface for bugs. Can we shrink it? of course, JavaScript has Optional chaining, destructuring assignment to make things less verbose.
const user = userResponse?.data?.user
const { firstname = '', lastname = '' } = user
const name = firstname + lastname
Isn't it slick? Modern? But beware when using things like this, the Babel transpile them as follows:
'use strict'
var _userResponse, _userResponse$data
var user =
(_userResponse = userResponse) === null || _userResponse === void 0
? void 0
: (_userResponse$data = _userResponse.data) === null ||
_userResponse$data === void 0
? void 0
: _userResponse$data.user
var _user$firstname = user.firstname,
firstname = _user$firstname === void 0 ? '' : _user$firstname,
_user$lastname = user.lastname,
lastname = _user$lastname === void 0 ? '' : _user$lastname
var name = firstname + lastname
TL;DR; When using transpiling, make sure you choose the one that is more optimal for your use case.
Know SMI and heap numbers
Numbers are weird. The ECMAScript standardizes numbers as 64-bit floating-point values, also known as double precision floating-point
or Float64
representation.
If the JavaScript engines store numbers in Float64 representation then it will lead to huge performance inefficiency. JavaScript Engines abstract the numbers such that its behavior matches Float64 exactly. The JavaScript engine executes integer operations much faster than compared to the float64
operations.
For more details, check this out.
TL;DR; Use SMI (small integers) wherever possible.
Evaluate Local Variables
Sometimes, folks think that it is readable to supply a value like this,
const maxWidth = '1000'
const minWidth = '100'
const margin = '10'
getWidth = () => ({
maxWidth: maxWidth - margin * 2,
minWidth: minWidth - margin * 2,
})
What if the getWidth
function is called multiple times, the value is getting computed every time when you call it. The above calculation is not a big deal and you wouldn't notice any performance impact because of that.
But in general, lesser the evaluation at the runtime better the performance is.
// maxWidth - (margin * 2)
const maxWidth = '980'
// minWidth - (margin * 2)
const minWidth = '80'
const margin = '10'
getWidth = () => ({
maxWidth,
minWidth,
})
lesser the evaluation (at runtime) better the performance.
Use Map instead of switch / if-else conditions
Whenever you want to check multiple conditions, use a Map
instead of switch
/ if-else
condition. The performance of looking up elements in a map
is much more faster than the evaluation of switch
and if-else
condition.
switch (day) {
case 'monday':
return 'workday'
case 'tuesday':
return 'workday'
case 'wednesday':
return 'workday'
case 'thursday':
return 'workday'
case 'friday':
return 'workday'
case 'saturday':
return 'funday'
case 'sunday':
return 'funday'
}
// or this
if (
day === 'monday' ||
day === 'tuesday' ||
day === 'wednesday' ||
day === 'thursday' ||
day === 'friday'
)
return 'workday'
else return 'funday'
Instead of both use this,
const m = new Map([
['monday','workday'],
['tuesday', 'workday'],
['wednesday', 'workday'],
['thursday', 'workday'],
['friday', 'workday'],
['saturday', 'funday'],
['sunday', 'funday']
];
return m.get(day);
TL; DR; Use Map instead of switch / if-else conditions
if-else ordering
For example if you are writing a React component, it is very common to follow this pattern.
export default function UserList(props) {
const { users } = props
if (users.length) {
// some resource intensive operation.
return <UserList />
}
return <EmptyUserList />
}
Here, we render <EmptyUserList />
when there are no users or render <UserList />
. I have seen people argue that we have to handle all the negative scenarios at first and then handle the positive ones. They often come up with an argument, it is clearer for any one who reads it and also it is much more efficient. That is the following code is more efficient than the previous one.
export default function UserList(props) {
const { users } = props
if (!users.length) {
return <EmptyUserList />
}
// some resource intensive operation
return <UserList />
}
But what if the users.length
always evaluate true. Use that first and then the negative condition.
TL;DR; While designing
if-else
condition, order them in such a way that the number of conditions evaluated is lesser.
Types are your best friends
JavaScript is both interpreted and compiled language. The compiler in order to produce more efficient binary requires type information. But being a dynamically typed language makes it difficult for the compilers.
The compilers when compiling the hot code (the code that is executed many times), makes some assumptions and optimise the code. The compiler spends some time to produce this optimised code. When these assumption fails, the compilers has to throw away the optimised code and fallback to the interpreted way to execute. This is time consuming and costly.
TL;DR; Use Monomorphic types always.
Others
Avoid recursion, sure they are awesome and more readable. But they also affect the performance.
Use memoization wherever and whenever possible.
Sometimes bitwise and unary operators give a slight edge in the performance. But they are really useful when your performance budget is very tight.