How to write high performance JavaScript.

Loading and Execution

In fact, most browsers of the day utilize single thread to both render UI and execute scripts. Thus, the longer time scripts execute, the longer browsers have to wait to response.

Generally speaking, this means each appearance of the <script> tag would force the page into waiting until the script is totally analysed and executed. Both downloading and rendering must stop and wait the script to complete. That’s because changes to the page might occur during the execution of the script.

Script Position

HTML4 norm indicates that <script> tag can be put in both <head> and <body>, in which <script> can appear multi times. E.g.

<html>
<head>
  <title>Script Example</title>
<script type="text/javascript" src="file1/js" />
<script type="text/javascript" src="file2/js" />
<script type="text/javascript" src="file3/js" />
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<p>Hello wordl!</p>
</body>
</html>

There is actually a severe performance issue in this seemingly normal code. Loading files in the <head> tag would block the rendring stage. Before the analysing process meets the <body> tag, it won’t render any parts of the page!

There is a good news as well as a bad news. The good one is that most modern web browsers allow parallel downloading JavaScript files, but it would still block other resource download, which is the bad news.

The take away here is to put all the <script> tags at the end of the <body> as much as possible. e.g.

<html>
<head>
  <title>Script Example</title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<p>Hello wordl!</p>
  <script type="text/javascript" src="file1/js" />
<script type="text/javascript" src="file2/js" />
<script type="text/javascript" src="file3/js" />
</body>
</html>

Grouping Scripts

Take the expense of HTTP request, downloading a single big file would be faster than downloading multi small files.

A large website or web app usually depends on several JavaScript files. By bundling up those files into a single one, you can improve the performance.

That’s why we need webpack today. Webpack is an open-source JavaScript module bundler. Its main purpose is to bundle JavaScript files for usage in a browser. As a web front-end developer, we break our projects into many components to facilitate our development. That will result in tons of JavaScript files, which is unfeasible to download each of them through tons of <script> tag. Thank to God, with the help of webpack, we can bundle those files into a single one. e.g. A bundle.js file. Also, by proper configuration, you can uglify the JavaScript file with the help of some plugins.

Data Access

By changing data storage location to gain the best read-and-write performance is a classic problem in computer science. Data storage location would affect data access speed greatly. JavaScript has the 4 following data access location.

Literal Value
Literal value only represent itself. Literal values in JavaScript includes: string, number, boolean, object, array, regex, null and undefined.
Local Variable
The storage units which is defined by developers with key word—— var.
Array Element
Stored in an array, indexed by number.
Object Member
Stored in an object, indexed by string.

The speed of access these four types data order is:

literal value > local value > array element > object member

The usual advice is reduce the usage of array element and object member, use literal value and local variable instead.

Identifier Resolution Performance

Resolving an identifier costs. The deeper an identifier is in a scope chain of current execute environment, the slower its access speed is. Therefore, accessing local variables are always the fastest, while accessing global data is always the slowest. Because global data locates at the end of the scope chain.

The best practice is: If a cross-scope value would be referenced multi times in a function. A local variable for it should be created. And also, with a proper variable name, your code would be more readable this way. Consider the following example:

function initUI() {
document.body.className = 'active'
document.getElementById("confirm").onclick = function() {
start()
}
}

In the above code, the global variable document has been referenced twice. It would take a long way to search this variable, because it’s at the end of the scope chain. We can rewrite the above code like this:

function initUI() {
var doc = document
doc.body.className = 'active'
doc.getElementById("confirm").onclick = function() {
start()
}
}

The number of visits to document reduced from 2 to 1. Since doc is a local variable, visiting doc is much faster than visiting document. As you can imagine how much the performance would be improved if a global variable was visited dozens of time!

Nested Members and Caching Object Member Values

The deeper a member is nested, the slower it is accessed. Execute a.b is always faster than a.b.c. If these properties were not the object’s own property, it would take more time to search on the prototype chain.

There is no need to read an object member multi times in a function. We still use local variable in this case.

function detectClass(element) {
return element.className === 'name1' || element.className === 'name2'
}

element.class was read twice in the above example. We can precache it in the function to reduce the number of reference.

function detectClass(element) {
var className = element.className
return className === 'name1' || className === 'name2'
}

This optimization skill is not recommended for methods. Because many methods depend on this to determine the execute environment. Caching a method in function would change the execute environment of it resulting in an error at last.

DOM Scripting

Why DOM is Sooooo slow?

DOM is an API used to handle XML and HTML, which is independent from programming language.

Browsers usually implement DOM and JavaScript independently. e.g. Chrome use webkit to render DOM, while use V8 engine as its JavaScript engine. So what’s the matter with the performance? Costs would incur as long as two seperate thing were connected to each other using API.

DOM Access and Modification

Modifying elements costs more than visiting them, due to the recalculation of geometric chracteristic.

Well, the worst situation is modifying in a loop.

function innerHTMLLoop() {
for(var count = 0; count<100; count++) {
document.getElementById('text').innerHTML += 'a'
}
}

We definitely need to avoid this situation. A more efficient way is to cache the modified content in the function. Write it to the DOM after the loop.

function innerHTMLLoop() {
var content = ''
for(var count = 0; count<100; count++) {
content += 'a'
}
document.getElementById('text') += content
}

That’s why React and Vue implemented their own virtual DOM. Before each time they modifiy the DOM, they merely do the modifications in their virtual DOM, which is way faster than the DOM. Only after that will they commit those changes to the DOM.

Repaints and Reflows

When DOM change affect an element’s geometric characteristic. Browser will recalculate its geometric characteristic. At the same time other elements’ positions are also affected by the change. Their new position also need to be recalculated. This process is called reflow. Repaint occurs immediately after reflow.

Not all changes to the DOM will affect geometric characteristic. e.g. an element’s background-color.

Repaints and reflows are both expensive operations. We should minimize their occurance.

When does a Reflow Happen?
Adding or removing visible elements.
Element’s position, size, or content change.
Page renders for the first time.
Browser’s window’s size change.

Then how to minimize the affection of reflows? Here’re some best prectices

Minimize CSS rules. The smaller its size is, the faster reflow is.
Reduce DOM’s depth. Cut unnecessary wrap elements.
Take the element with complicated out of the normal document flow.
Bunch multi DOM changes into a single change to reduce DOM changes.
Trade-off between performance and fluency. Moving 1pixel per millisecond demand 4 times more work of reflow than moving 4pixel per 4ms. You need to approach a blance between them.

Responsive Interfaces

Nothing could make people more despair than a still page after hitting the submit button. A focus point being of great importance is making sure the responsive speed of the web page is fast enough.

But for some times, a long-running task is inevitable. In the pre-HTML5 era, We need a timer to split the task into several smaller task which can run without a realization of its existence. Now, we have a second way to handle this problem.

Web Worker

Since the birth of JavaScript, there hadn’t a method to run code without the browser UI thread. The appearance of Web Workers API broke this situation.

Each worker run task on its own thread rather than the browser UI thread.

Worker Environment

Since web workers are not bind with the UI thread, which means it can’t visit many resources of the web browser. Each worker has each own run time environment. Thus, you can’t create a worker with current JavaScript code. Instead, you need a completely independent JavaScript file, which contains the task for the worker to complete. To create a worker, the URL of the file needs to be passed to it.

var worker = new Worker("task.js")

Once this snippet were executed, a new thread and run time environment would be created for the file.

Worker Communication

Worker communicates with web page through event API. The web page can transfer date through method postMessage() to the worker. The worker has a event handler to handle the message sent by the web page.

var worker = new Worker("task.js")
worker.onmessage = function(event) {
console.log(event.data)
}
worker.postMessage("Hey, worker!")

The worker receive messages through triggering the message event. Worker can send some message back to the page through its postMessage() method.

// inner task.js
self.onmessage = function(event) {
self.postMessage("Hey, browser!")
}

Sum Up

To sum up, we’ve talked about many aspect where we should pay attention to. On the load and execution stage, we should put all the <script> tags at the end of <body>. Also, we should bundle all of our project files using webpack to minimize the number of http requests.

Next, I explained how to access data more efficiently in JavaScript.

Then, I explained why manipulating DOM is slow and advised you bunching multi DOM changes in to a single one. What’s more, I introduced two expensive process while page rendering and how to minimize their costs.

At last, a new web API to run JavaScript in new thread to avoid halt of rendering when running the code.

Happy coding!