The Elements of JavaScript Style

Part One

Douglas Crockford
The Department of Style
2005-09-19

Programming is difficult. At its core, it is about managing complexity. Computer programs are the most complex things that humans make. Quality is a illusive and elusive.

Good architecture is necessary to give programs enough structure to be able to grow large without collapsing into a puddle of confusion, but the ways in which we express the details of a program are equally important. A program's true nature can be concealed by sloppy coding. Only when the presentation of a program is clear can we have any hope of reasoning correctly about its efficiency, or security, or correctness.

The classic work in literary style is William Strunk's The Elements of Style, a skinny manual on writing in English, with advice on usage, composition, and form. The idea of style was applied unsuccessfully to programming in Kreitzberg and Shneiderman's The Elements of FORTRAN Style in 1972, and then brilliantly in Kernighan and Plauger's The Elements of Programming Style in 1978:

Good programming cannot be taught by preaching generalities. The way to learn to program well is by seeing, over and over, how real programs can be improved by the application of a few principles of good practice and a little common sense.

They took programs culled from other programming textbooks, which they criticized and improved.

When we talk of style here, we are not talking about fads and fashions, nor are we talking about CSS or conventions of layout or typography. We are talking about timeless qualities of expression which can substantially increase the value of a codebase. For companies whose valuations are inextricably bound to their codebases, style should be a vital concern.

We use many programming languages, but in a way, JavaScript is the most important. It is the language of the browser. When people come to our site, they are (perhaps unknowingly) inviting our JavaScript programs to execute in their machines. We have a special obligation to make those programs good.

There are no good texts on JavaScript programming. Most of the people on the web who are producing JavaScript programs learned it by copying really bad examples from bad books, bad websites, and bad tools. We have an amazingly good community of JavaScript programmers here, but still we can benefit from better practice of style.

To demonstrate this, I will be taking programs from our public website, and showing how they can be improved. It is not my intention to embarrass anyone. My intention is only to show the value of style by example. I will be revealing no secrets: I will be showing you what we are already transmitting to everyone in the world.

 

The following examples were extracted from www.yahoo.com on 2005-09-19.

<script language=javascript><!--
     lck='',
     sss=1127143538,
     ylp='p.gif?t=1127143538&_ylp=A0Je5ipy2C5D54AAwVX1cSkA',
     _lcs='';
--></script> 

This script block uses the language attribute. This was a feature that was introduced by Microsoft in order to support VBScript. Netscape then adopted it to support its own nonstandard deviations. W3C did not adopt the language attribute, favoring instead a type attribute which takes a MIME type. Unfortunately, the MIME type was not standardized, so it is sometimes "text/javascript" or "application/ecmascript" or something else. Fortunately, all browsers will always choose JavaScript as the default programming language, so it is always best to simply write <script>. It is smallest, and it works on the most browsers.

The use of HTML comments in scripts dates further back to a transitional problem between Netscape Navigator and Netscape Navigator 2. The latter introduced the <script> tag. However, users of the former would see the script as text because of the HTML convention that unrecognized markup is ignored. The <!-- comment hack stopped being necessary by the time Netscape Navigator 3 came out. It certainly is not needed now. It is ugly and a waste of space.

The comma operator was borrowed, like much of JavaScript's syntax, from C. The comma operator takes two values and returns the second one. Its presence in the language definition tends to mask certain coding errors, so compilers tend to be blind to some mistakes. It is best to avoid the comma operator, and use the semicolon statement separator instead.

In this case, we are defining some global variables. JavaScript, when assigning to an unknown variable, creates a new global variable instead of generating an error. This was, in hindsight, a mistake. It is best to avoid mistakes, even when they are standard mistakes. We should be explicit in declaring the variables. It will cost us 4 characters, but it is the right thing to do.

<script>
var lck = '3ek6b0i2he2a5eh3/o',
    sss = 1126894256,
    ylp = 'p.gif?t=1126894256&_ylp=A0Je5iOwCitDw2YBX331cSkA',
    _lcs = '94040';
</script>

From that we can derive this principle:


Avoid archaic constructions.


The next example looks at a cookie class constructor. It creates an object having a get method and a set method.

function yg_cookie() {
    this.get = function (n) {
var s,
e,
v = '',
c = ' ' + document.cookie + ';';
if ((s = c.indexOf((' ' + n + '='))) >= 0) {
if ((e = c.indexOf(';',s)) == -1)
e = c.length;
s += n.length + 2;
v = unescape(c.substring(s, e));
}
return (v);
}
this.set = function (n,v,e) {
document.cookie = n + "=" + escape(v) +
";expires=" + (new Date(e * 1000)).toGMTString() +
";path=/" + ";domain=www.yahoo.com";
}
}
var _yc = new yg_cookie();

JavaScript's if statement is similar to C's: it can take statements or blocks. The problem with using statements is that a common error is very difficult to detect. It is better to write

if ((e = c.indexOf(';', s)) == -1) 
    e = c.length;

as

if ((e = c.indexOf(';', s)) == -1) {
    e = c.length;
}

The use of blocks avoids situations like this:

if ((e = c.indexOf(';', s)) == -1) 
    e = c.length;
    s += n.length + 2;

It might appear that s is only incremented when indexOf returns -1, but this is not the case. Bugs like that can be very expensive to find, but can be inexpensively avoided by always using braces to indicate structure.


Always use blocks in structured statements.


Another bad habit that JavaScript inherited from C is the assignment expression. It appears to streamline code, but it can make control flow more difficult to understand. The get method gets clearer if we separate the computation of s and e from their uses.

this.get = function (n) {
var v = '',
c = ' ' + document.cookie + ';',
s = c.indexOf((' ' + n + '=')),
e = c.indexOf(';', s);
if (s >= 0) {
if (e == -1) {
e = c.length;
}
s += n.length + 2;
v = unescape(c.substring(s, e));
}
return (v);
}

We can now see that there are excess parens around the argument to indexOf where s is computed. (There are also unnecessary parens in the return statement.) But more importantly, it is easier to see what the purpose of if (e == -1) is: If a final semicolon is not found in the cookie, then assume that the cookie ends at the end of the string. However, when we computed c, we appended a semicolon to the cookie, which guarantees that the condition the if is anticipating will never happen. So we can remove the if.


Avoid assignment expressions.


When a function is assigned to a value, as in this.get = function (n) { ... } it should end with a semicolon just like all assignment statements.

function yg_cookie() {
this.get = function (n) {
var v = '',
c = ' ' + document.cookie + ';',
s = c.indexOf((' ' + n + '='));
if (s >= 0) {
s += n.length + 2;
v = unescape(c.substring(s, c.indexOf(';', s)));
}
return v;
};
this.set = function (n,v,e) {
document.cookie = n + "=" + escape(v) +
";expires=" + (new Date(e * 1000)).toGMTString() +
";path=/" + ";domain=www.yahoo.com";
};
}
var _yc = new yg_cookie();

Finally, we see that yg_cookie is a constructor that produces a single stateless object. We do not need a constructor function at all. We can simply make an empty object and augment it by assigning the methods to it.

var _yc = new Object();
_yc.get = function (n) {
var v = '',
c = ' ' + document.cookie + ';',
s = c.indexOf((' ' + n + '='));
if (s >= 0) {
s += n.length + 2;
v = unescape(c.substring(s, c.indexOf(';', s)));
}
return v;
};
_yc.set = function (n,v,e) {
document.cookie = n + "=" + escape(v) +
";expires=" + (new Date(e * 1000)).toGMTString() +
";path=/" + ";domain=www.yahoo.com";
};

If we do not need to support Netscape 3 or IE 4, then we can do that more elegantly by using the object literal notation.

var _yc = {
get: function (n) {
var v = '',
c = ' ' + document.cookie + ';',
s = c.indexOf((' ' + n + '='));
if (s >= 0) {
s += n.length + 2;
v = unescape(c.substring(s, c.indexOf(';', s)));
}
return v;
},
set: function (n,v,e) {
document.cookie = n + "=" + escape(v) +
";expires=" + (new Date(e * 1000)).toGMTString() +
";path=/" + ";domain=www.yahoo.com";
}
};

Use object augmentation.


At this point we have a couple of methods for manipulating cookies. It is surprising then that the very next thing we find is code that does cookie manipulation without taking advantage of the methods we just defined.

var b,
l = '', n = '0',
y;
y = ' ' + document.cookie + ';';
if ((b = y.indexOf(' Y=v')) >= 0) {
y = y.substring(b, y.indexOf(';', b)) + '&';
if ((b = y.indexOf('l=')) >= 0) {
l = y.substring(b + 2, y.indexOf('&', b));
if ((b = y.indexOf('n=')) >= 0)
n = y.substring(b + 2, y.indexOf('&', b));
}
}

It even replicates the same techniques that we saw earlier. It is likely that both chunks of code were adapted from the same faulty original. We can improve it by taking advantage of our recent work:

var l = '',
    n = '0',
    y = _yc.get('Y') + '&',
    b = y.indexOf('l=');
if (b >= 0) {
    l = y.substring(b + 2, y.indexOf('&', b));
    b = y.indexOf('n=');
    if (b >= 0) {
        n = y.substring(b + 2, y.indexOf('&', b));
    }
}

Code reuse is the Holy Grail of Software Engineering. We can imagine great efficiencies obtained by avoiding the vast amount of hand work required by the current state of the art. Here we found a failure to use a method that had been defined adjacent to the place where it was needed.


Use common libraries.


The structure of software systems tend to reflect the structure of the organizations that produce them. In this case, we see evidence of obvious inefficiencies caused by an organization that lacks awareness of the interconnectedness of its own processes. The application of style is critical, because it is only possible to fit the pieces together properly if we can understand what the pieces are.