Building a Low Memory Web Application

Dmitri Lau
Share

With the rise in mobile devices and tablets, web applications are frequently being loaded in slow and low memory environments. When building a web application, one may consider making design choices which reduce the amount of memory consumed by your application, so that the user experience remains fast and responsive.

The general idea of reducing your application’s footprint is to reduce the amount of code in your application, thereby reducing the amount of physical memory it occupies. This includes functions, variables, event handlers, housekeeping code, etc.

Tip 1: Use One Event Listener Over Multiple Event Listeners

It is common to do the following to add event listeners to multiple nodes of the same group.

$("#list .countries").on("click", function() {
  $("box").value = this.innerHTML;
});

If your website has 100 nodes, then you are adding an event listener 100 times. That means each of those 100 nodes is modified to respond to an onclick event, and every modification consumes extra memory in the form of reference pointers and state flags.

Next time consider doing it like this:

$("#list").on("click", function() {
  if($(this).hasClass("countries")) $("box").value = this.innerHTML;
});

With this, you only need to listen to one parent node for the onclick event and saved 99 other nodes from getting fat, at the cost of a negligible amount of execution time.

Tip 2: Use Inline Event Handlers Over External Event Handlers

<div onclick="menus.activate(event);">activate</div>

This tip will certainly get some people worked up. It is taken for granted that one should migrate away from inline event handlers, because it mixes behavior with content. With no disrespect to the “best practice” reasons for not using them, you will find that inline event handlers can add a lot of savings to your footprint.

First of all, you won’t need to write event registration code (i.e., addEventListener()/removeEventListener()) which will save you several lines of code at the very least. Your application also won’t need to spend time executing your JavaScript (your event registration functions), which is much slower than native code (the DOM parser). And as a side benefit, you won’t have to worry about memory leaks due to forgetting to unregister a handler, since the handler dies with the node.

You also won’t need to assign an ID to the node in order to reach it within your code, nor do you need to walk the DOM to find that node (a method popularized by jQuery). You just saved some footprint there, as well as preventing your application from doing extra work.

And, since inline event handlers can preserve context, it allow you to conserve memory by eliminating the need to create closures to encapsulate context. You may not be aware, but whenever you wrap an event handler with an anonymous function, you are creating that closure:

node.addEventListener("click", function(e) {
  menus.activate(e);
}, false);

Some libraries hide this behind a helper function, but it is the same issue nonetheless:

node.addEventListener("click", menus.activate.bind(menus), false);

The problem escalates because some libraries will create a new anonymous function for every event handler registration, which means the number of closures will grow linearly with the number of handler registrations, which means precious memory is wasted. With inline event handlers, you don’t need to create the extra function or the closure.

<div onclick="menus.activate(event);">activate</div>

Tip 3: Use Templates Over DOM Creation

If you are creating anything other than a few nodes, it takes less code to generate a DOM tree by assigning a string of HTML to the innerHTML property, than it does to create the nodes one by one with document.createElement(). And, you don’t have to worry about your HTML content being confined within your JavaScript code, because you can hide it safely within your website, as shown below.

<body>
  <script type="text/html" id="mytemplate1">
    <div>hello</div>
  </script>
</body>

Notice how the HTML content won’t be rendered, because it is placed inside a <script> tag. The <script> tag also uses a text/html mimetype so that the browser won’t confuse it for JavaScript. You can retrieve the string with the following code.

var text = document.getElementById("mytemplate1").innerHTML;

Often times we don’t want just plain text, but want text with embedded symbols that allow for dynamic variable substitutions. That is the job of the template library and it makes DOM creation using templates much more powerful. A small template library should reduce your footprint over DOM creation code in the long run.

Tip 4: Consider Stateless Singletons over Stateful Objects

Applications are composed of components, and each component is typically a DOM node backed by a JavaScript object to store data. The more components there are, the more JavaScript objects there are. But, if you can share the same JavaScript object with multiple DOM nodes, then you can save some memory by reusing the JavaScript object. Achieving this requires designing your JavaScript objects to behave like singletons. The component will have no state, and only serve to call the singleton to perform a generic function. Alternatively, it can store some basic data as an attribute on the DOM node and the singleton can read that data from the node and act on it.

In a more complex scenario, the DOM node can store a unique ID as an attribute and the singleton can map the ID to a complex data object stored elsewhere. (Further complexity is best left for a future article.)

This technique is most suited for components where a lot of them are used at the same time such as list items, or for components which are very simple and stateless such as buttons. For example:

<input type="button" onclick="GenericButton.onclick(event);" data-command="copy">
GenericButton.onclick = function(e) {
  e = e.target || e.srcElement;
  var cmd = e.getAttribute("data-command");
  if(cmd == "copy") //...
};

Tip 5: Make Full Use of Prototypical Inheritance

If you are instantiating a lot of objects of the same type, choose prototypical inheritance over property injection. When you inject properties into an object, you are copying references onto each object. This causes the number of references to grow linearly with the number of objects.

function A() {
  this.value = 100;  //injecting value into this object
}
var obj1 = new A();
var obj2 = new A();

If you instead, allow those properties to be inherited, those references only exist once on the prototype of that object. Then, the number of references does not grow linearly unless the property’s value is modified later.

function A() {
}
A.prototype.value = 100;
var obj1 = new A();
var obj2 = new A();

Tip 6: Use the Publish-Subscribe System to Simplify Communication

Rather than use the observer pattern (e.g., addActionListener()) to communicate between components, consider using the publish-subscribe pattern to communicate between them. The publish-subscribe pattern uses less code to engage in communication and allows your application to be more decoupled, thereby not requiring as much code to maintain the coupling. There are lots of implementations of the publish-subscribe system available on the web that are thrift with memory that you can use.

Tip 7: Use Smaller Libraries When Possible

This last tip is the most obvious. Large libraries consume a lot of memory, and small libraries consume less. There is a site dedicated to showcasing tiny libraries called microjs where you may be able to find a library that just serves up your needs and nothing more.