Feel the Burn: Five Weightloss Tips to Keep Your JavaScript Trim

JavaScript minification is now commonplace on the web, thanks to tools like JSMin, YUI Compressor, Google Closure Compiler, and the Microsoft Ajax Minifier. These useful tools do things like strip out comments, remove unnecessary whitespace, and shorten local variable names to single characters.

There are a lot of little things that a developer can do to help them along with the goal of getting the slimmest, trimmest script file. Let’s take a look at five techniques that you can employ to get JavaScript to burn a little extra fat.

1. Declare multiple variables with one var

It’s pretty common to see this in any given JavaScript source file:

var samsAge = 27;
var bettysAge = 32;
var combinedAge = samsAge + bettysAge;
if (samsAge > bettysAge) {
var difference = samsAge - bettysAge;
alert(difference);
}

By combining all of the variables in the same scope into one variable declaration, we can save around 4 characters per variable.

var samsAge = 27,
bettysAge = 32,
combinedAge = samsAge + bettysAge,
difference;
if (samsAge > bettysAge) {
difference = samsAge - bettysAge;
alert(difference);
}

Compared:

// Multiple declarations
var a=27;var b=32;var c=a+b;if(a>b){var d=a-b;alert(d);}
// One declaration
var a=27,b=32,c=a+b,d;if(a>b){d=a-b;alert(d);}

Impact — Medium

This one may seem like small savings, but the savings add up quickly in larger files. Depending on which minifier you’re using, the library may combine adjacent var statements together for you. Keep in mind that minifiers don’t generally go through the entire scope to combine variable declarations together, so you will eke out additional savings by following this practice.

Risk — Low

Front-loading variable declarations is a practice programmers have traditionally recommended to avoid duplicate variables and to keep things tidy. Because, unlike many languages, JavaScript doesn’t create a scope inside the body of control statements (if, for, while, etc), so there’s no impact to efficiency.

Is it worth it?

Most of the time.

2. Use literals for Object, Array, and RegExp

You sometimes see developers create Objects, Arrays, and RegExps using the new keyword and the appropriate constructor function, like so:

var list = new Array(1, 2, 3),
obj = new Object(),
regex = new RegExp("\d+", "g");
obj.foo = "bar";
obj.abc = 123;

Let’s rewrite that code, this time using literals:

var list = [1, 2, 3],
obj = { foo: "bar", abc: 123 },
regex = /\d+/g;

Compared:

// Constructors
var a=new Array(1,2,3),b=new Object,c=new RegExp("\d+","g");b.foo="bar";b.abc=123;
// Literals
var a=[1,2,3],b={foo:"bar",abc:123},c=/\d+/g;

Impact—High

Using literals in your code instead of the chunkier constructors will make your JavaScript shed pounds faster than almost anything else that you can do. Literals allow for much more concise code, so take advantage!

Risk—Low

Using literals instead of constructors usually improves readability and won’t harm performance. When possible, initialize Object properties and Array members inside of the literal, and you’ll save whole lines of code.

Is it worth it?

Yes, always!

3. One-statement prototypes

Prototype declarations are one of the single most verbose aspects of JavaScript. By using a single object literal, you can trim away at your code:

function Dog(name) {
this.name = name;
}
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
alert("bark bark!");
};
Dog.prototype.sit = function() {
this.sitting = true;
};

By declaring the prototype as a single Object literal, we can see some significant gains:

function Dog(name) {
this.name = name;
}
Dog.prototype = {
constructor : Dog,
speak : function() {
alert("bark bark!");
},
sit : function() {
this.sitting = true;
}
};

Compared:

// Line-by-line
function Dog(a){this.name=a;}Dog.prototype.constructor=Dog;Dog.prototype.speak=function(){alert("bark bark!");};Dog.prototype.sit=function(){this.sitting=true;};
// Literal prototype
function Dog(a){this.name=a;}Dog.prototype={constructor:Dog,speak:function(){alert("bark bark!");},sit:function(){this.sitting=true;}};

Impact — Medium

You can get solid savings by using a literal to declare prototypes, but there’s generally a limited amount of code in a project to which you can apply this technique.

Risk — Medium

While there’s almost zero risk of introducing bugs by switching to one-statement literal prototypes, they can be less readable and also introduce an interesting problem with inheritance. While it’s easy to use one-statement literal prototypes for base types, you can’t use this technique for when the prototype isn’t Object (when it inherits from another type).

We can make a modification to a typical inheritance helper function to let us use literals:

function extend(supertype, subtype, overrides) {
var intermediate = function() { },
prop;
intermediate.prototype = supertype.prototype;
subtype.prototype = new intermediate;
subtype.prototype.constructor = subtype;
if (overrides) {
for (prop in overrides) {
subtype[prop] = overrides[prop];
}
}
}

With the new extend function, I can now establish inheritance and fill out my prototype at the same time:

function Dog(name) {
this.name = name;
}
Dog.prototype = {
constructor : Dog,
speak : function() {
alert("woof!");
},
sit : function() {
this.sitting = true;
}
};
function Poodle(name) {
Dog.call(this, name);
}
extend(Dog, Poodle, {
growl : function() {
alert("yap yap yap");
},
sit : function() {
this.indignant = true;
this.sitting = false;
}
});

We’ve now declared both a super- and sub-type using literals instead of Blah.prototype.blah = “ad nauseam”. There’s been a cost though—extra lines of code needed to be added to the inheritance function, and there’s a small performance hit for using the for…in loop that wasn’t there before.

Is it worth it?

For large or complex projects, yes, definitely. For small projects, no, probably not.

4. Chaining assignments

It’s not uncommon to see code like this:

var header = document.getElementById("header"),
footer = document.getElementById("footer"),
clickHandler = function(e) {
alert("I was clicked!");
};
header.onclick = clickHandler;
footer.onclick = clickHandler;
header = null;
footer = null;

To send the above code on a diet, we can take advantage of the assignment operator always returning the assigned value:

var header = document.getElementById("header"),
footer = document.getElementById("footer");
footer.onclick = header.onclick = function(e) {
alert("I was clicked!");
};
footer = header = null;

Compared:

// Separate assignments
var a=document.getElementById("header"),b=document.getElementById("footer"),c=function(e){alert("I was clicked!");};a.onclick=c;b.onclick=c;a=null;b=null;
// Chained assignments
var a=document.getElementById("header"),b=document.getElementById("footer");b.onclick=a.onclick=function(e){alert("I was clicked!");};b=a=null;

Impact — Low

This technique is much more situational that the other ones mentioned so far, though it is effective at reducing size and, as a helpful side effect, also improves the performance of your code.

Risk — Low

There’s no added risk to using this technique versus doing multiple assignments to the same referenced variable (or built-in value).

Is it worth it?

Most of the time. Chaining assignments can either make code read more cleanly or make it much more difficult to read, depending on how many terms are involved, so you’ll want to factor that into your decision.

5. Avoid repeat property look-ups

I’ve seen a lot of code that looks like this:

var obj = { a: "foo", b: "bar", c: "foo" },
values = [],
prop;
for (prop in obj) {
alert("The value is " + obj[prop] + ", and its length is " + obj[prop].length);
values.push(obj[prop]);
alert("And " + obj[prop] + " makes " + values.length);
}

By taking out the repeated property look-up, we can make this more concise:

var obj = { a: "foo", b: "bar", c: "foo" },
values = [],
prop,
item;
for (prop in obj) {
value = obj[prop];
alert("The value is " + value + ", and its length is " + value.length);
values.push(value);
alert("And " + value + " makes " + values.length);
}

Compared:

// With repeated look-ups
var a={a:"foo",b:"bar",c:"foo"},b=[],c;for(c in a){alert("The value is "+a[c]+", and its length is "+a[c].length);b.push(a[c]);alert("And "+a[c]+" makes "+b.length);}
// With variable assignment
var a={a:"foo",b:"bar",c:"foo"},b=[],c,d;for(c in a){d=a[c];alert("The value is "+d+", and its length is "+d.length);b.push(d);alert("And "+d+" makes "+b.length);}

Impact — Low to Medium

With this technique, your code won’t get any trimmer unless there are four or more look-ups. (In fact, it will be more characters if there are only two look-ups and exactly the same it there are three.) There is a performance gain to using variable assignment instead of repeat look-ups, so this may be a technique that you consider for other reasons.

Risk — Low

There’s no considerable risk to using this technique, and it tends to make code more readable.

Is it worth it?

For three or more look-ups, yes! If you’re dealing with one or two look-ups, probably not.

All together now

Minification means that things that we’ve traditionally thought of as keeping our code tiny, such as shortening variable names, don’t matter as much. (In fact, that just makes your un-minified source hard to read and doesn’t offer any gain in minified size.) Instead, we need to focus on improving our program’s flow in minifier-friendly ways—and hopefully these five techniques will help you do just that.

No comments.

Leave a Reply