I see a lot of new or existing JavaScript developers are not coding in the proper way in my office. I always see things which can be improved on their codebase, but I can't help them much because I moved to the most busiest Ruby team. I understand that JavaScript is the most misunderstood language, but many people is able to catch it up fairly well.
Two years before I left JavaScript, I was intended to do presentation about what are the most common mistakes in the my office. However, I failed to do it because of some reasons. A few months ago I see there are a Ruby and Rails Style Guide. I think I should do one on JavaScript Style Guide.
There are many sources I've combined into the below documents. You could see the link for more details in each topic and in the References section. The main ones are from Google Javascript Style Guide, JavaScript Patterns from Addy Osmani, JavaScript Object-Oriented Programming from Mozilla. Here, I took most of the good parts and write up in a bit more details to clarify.
Anyway, welcome your feedbacks. (The work is in progress.)
- JavaScript Language Rules
- JavaScript Style Rules
- JavaScript Object Oriented Programming
- JavaScript Common Patterns
- References
```JavaScript
function test() {
a = "hello";
}
alert(a);
```
```JavaScript
goog.example.SECONDS_IN_A_MINUTE = 60;
```
```JavaScript
MyClass.prototype.myMethod = function () {
return 42;
};
var x = {
'i': 1,
'j': 2
};
var THINGS_TO_EAT = [apples, oysters, sprayOnCheese];
```
```JavaScript
function hypotenuse(a, b) {
function square(x) { return x*x; }
return Math.sqrt(square(a) + square(b));
}
```
```JavaScript
if (x) {
function foo() {}
}
```
-
It is not part of ECMAScript. Do this instead:
if (x) { var foo = function () {} }
```JavaScript
var x = new Boolean(false);
if (x) {
alert('hi'); // Shows 'hi'.
}
```
-
However, it is very useful for casting things to number, string and boolean.
var x = Boolean(0); if (x) { alert('hi'); // This will never be alerted. } typeof Boolean(0) == 'boolean'; typeof new Boolean(0) == 'object';
```JavaScript
Foo.prototype.bar = function () {
/* ... */
};
```
```JavaScript
var userOnline = false;
var user = 'nusrat';
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', 'http://chat.google.com/isUserOnline?user=' + user, false);
xmlhttp.send('');
// Server returns:
// userOnline = true;
if (xmlhttp.status == 200) {
eval(xmlhttp.responseText);
}
```
```JavaScript
with (foo) {
var x = 3;
return x;
}
```
Instead of this:
```JavaScript
with (ooo.eee.oo.ah_ah.ting.tang.walla.walla) {
bing = true;
bang = true;
}
```
you could do this:
```JavaScript
var o = ooo.eee.oo.ah_ah.ting.tang.walla.walla;
o.bing = true;
o.bang = true;
```
```JavaScript
function foo() {
this.bar = "hello"
};
foo();
alert(bar); // Shows 'hello'.
```
-
the scope of the caller (in eval)
foo = { name: "hi", execute: function () { eval("alert(this.name)"); } } foo.execute(); // Show 'hi'.
-
a node in the DOM tree
<div onclick="alert(this.innerHTML)">Hello World</div> // Show 'Hello World' when click that div.
-
a newly created object (in a constructor)
var Foo = function(name) { this.name = name; // this refers to the new object };
-
some other object (if function was call()ed or apply()ed)
var x = 10; var o = { x: 15 }; function f() { alert(this.x); } f(); // Show '10' f.call(o); // Show '15'
-
It's often to get it wrong, limit its use to those places where it is required:
- in constructors
- in methods of objects (including in the creation of closures)
```JavaScript
function printArray(arr) {
for (var key in arr) {
print(arr[key]);
}
}
printArray([0,1,2,3]); // This works.
var a = new Array(10);
printArray(a); // This is wrong.
a = document.getElementsByTagName('*');
printArray(a); // This is wrong.
a = [0,1,2,3];
a.buhu = 'wine';
printArray(a); // This is wrong again.
a = new Array;
a[3] = 3;
printArray(a); // This is wrong again.
```
-
Always use normal for loops when using arrays.
function printArray(arr) { for (var i = 0, l = arr.length; i < l; i++) { print(arr[i]); } }
```JavaScript
var myString = 'A rather long string of English text, an error message \
actually that just keeps going and going -- an error \
message to make the Energizer bunny blush (right through \
those Schwarzenegger shades)! Where was I? Oh yes, \
you\'ve got an error and all the extraneous whitespace is \
just gravy. Have a nice day.';
```
-
Use string concatenation instead:
var myString = 'A rather long string of English text, an error message ' + 'actually that just keeps going and going -- an error ' + 'message to make the Energizer bunny blush (right through ' + 'those Schwarzenegger shades)! Where was I? Oh yes, ' + 'you\'ve got an error and all the extraneous whitespace is ' + 'just gravy. Have a nice day.';
```JavaScript
// Length is 3.
var a1 = new Array(x1, x2, x3);
// Length is 2.
var a2 = new Array(x1, x2);
// If x1 is a number and it is a natural number the length will be x1.
// If x1 is a number but not a natural number this will throw an exception.
// Otherwise the array will have one element with x1 as its value.
var a3 = new Array(x1);
// Length is 0.
var a4 = new Array();
```
-
To avoid these kinds of weird cases, always use the more readable array literal.
var a = [x1, x2, x3]; var a2 = [x1, x2]; var a3 = [x1]; var a4 = [];
-
Object constructors don't have the same problems, but for readability and consistency object literals should be used.
var o = new Object(); var o2 = new Object(); o2.a = 0; o2.b = 1; o2.c = 2; o2['strange key'] = 3;
Should be written as:
var o = {}; var o2 = { a: 0, b: 1, c: 2, 'strange key': 3 };
```JavaScript
function initUI() {
var bd = document.body,
links = document.getElementsByTagName("a"),
i= 0,
len = links.length;
while (i < len) {
update(links[i++]);
}
document.getElementById("go-btn").onclick = function () {
start();
};
bd.className = "active";
}
```
It could be written in this way by declaring local variables:
```JavaScript
function initUI() {
var doc = document,
bd = doc.body,
links = doc.getElementsByTagName("a"),
i = 0,
len = links.length;
while(i < len) {
update(links[i++]);
}
doc.getElementById("go-btn").onclick = function () {
start();
};
bd.className = "active";
}
```
* Older browsers, IE and FF 3.5, incur a performance penalty with each additional step into the prototype chain.
* The nested members such as "window.location.href" causes the javascript engine to go through the object member resolution process two times (the number of dots).
```JavaScript
function toggle(element) {
if (YAHOO.util.Dom.hasClass(element, "selected")) {
YAHOO.util.Dom.removeClass(element, "selected");
return false;
} else {
YAHOO.util.Dom.addClass(element, "selected");
return true;
}
}
```
It would be written in this way:
```JavaScript
function toggle(element) {
var Dom = YAHOO.util.Dom;
if (Dom.hasClass(element, "selected")) {
Dom.removeClass(element, "selected");
return false;
} else {
Dom.addClass(element, "selected");
return true;
}
}
```
-
A closure function requires more memory overhead in a script than a nonclosure function because it keeps a pointer to its enclosing scope. As a result, it might be a problem, circular reference and thus create memory leak, on IE 6.
-
Although it might not create memory leak on other browsers, it incurrs a performance penalty with each access of out-of-scope identifiers.
function foo(element, a, b) { /* This event handler is a closure, can access a and b. It keeps a reference to element, a, and b although it never uses element. element also keeps a reference to the closure as well. */ element.onclick = function() { /* uses a and b */ }; }
It could be written in this way:
function foo(element, a, b) { element.onclick = bar(a, b); } function bar(a, b) { return function() { /* uses a and b */ } }
-
Properties and methods
-
Private members should be named with a trailing or prefixed underscore.
-
Like public members, protected members should be named without underscore.
// File 1. /** @constructor */ AA_PublicClass = function () { }; /** @private */ AA_PublicClass.staticPrivateProp_ = 1; /** @private */ AA_PublicClass.prototype.privateProp_ = 2; /** @protected */ AA_PublicClass.staticProtectedProp = 31; /** @protected */ AA_PublicClass.prototype.protectedProp = 4; // File 2. /** * @return {number} The number of ducks we've arranged in a row. */ AA_PublicClass.prototype.method = function () { // Legal accesses of these two properties. return this.privateProp_ + AA_PublicClass.staticPrivateProp_; }; // File 3. /** * @constructor * @extends {AA_PublicClass} */ AA_SubClass = function () { // Legal access of a protected static property. AA_PublicClass.staticProtectedProp = this.method(); }; goog.inherits(AA_SubClass, AA_PublicClass); /** * @return {number} The number of ducks we've arranged in a row. */ AA_SubClass.prototype.method = function () { // Legal access of a protected instance property. return this.protectedProp; };
-
-
Namespacing
-
Use namespaces for global code to prevent collisions during code integration from two projects.
-
Always prefix with a unique namespace name.
var sloth = {}; sloth.sleep = function () { ... };
-
Alias long type names to improve readability Use local aliases, the last part of the type, for fully-qualified types if doing so improves readability.
/** * @constructor */ some.long.namespace.MyClass = function () { }; /** * @param {some.long.namespace.MyClass} a */ some.long.namespace.MyClass.staticHelper = function(a) { ... };
Never create aliases in the global scope. Use them only in function blocks.
myapp.main = function () { var MyClass = some.long.namespace.MyClass; var staticHelper = some.long.namespace.MyClass.staticHelper; staticHelper(new MyClass()); };
Do not alias namespaces.
myapp.main = function () { var namespace = some.long.namespace; namespace.MyClass.staticHelper(new namespace.MyClass()); };
-
-
Filenames should be all lowercase. Filenames should end in .js, and should contain no punctuation except for - or _ (prefer - to _).
```JavaScript
if (something) {
// ...
} else {
// ...
}
```
-
Single-line array and object initializers are allowed when they fit on a line:
var arr = [1, 2, 3]; // No space around []. var obj = {a: 1, b: 2, c: 3}; // No space around {}.
-
Multiline array initializers and object initializers are indented 2 spaces.
// Object initializer. var inset = { top: 10, right: 20, bottom: 15, left: 12 }; // Array initializer. this.rows_ = [ '"Slartibartfast" <fjordmaster@magrathea.com>', '"Zaphod Beeblebrox" <theprez@universe.gov>', '"Ford Prefect" <ford@theguide.com>', '"Arthur Dent" <has.no.tea@gmail.com>', '"Marvin the Paranoid Android" <marv@googlemail.com>', 'the.mice@magrathea.com' ]; // Used in a method call. goog.dom.createDom(goog.dom.TagName.DIV, { id: 'foo', className: 'some-css-class', style: 'display:none' }, 'Hello, world!');
-
Always prefer non-aligned initialization. For example:
CORRECT_Object.prototype = { a: 0, b: 1, lengthyName: 2 };
Not like this:
WRONG_Object.prototype = { a : 0, b : 1, lengthyName: 2 };
-
Whitespace
-
No tabs. Indent blocks with two spaces.
-
Spaces after commas and semicolons.
-
Separate binary operators with spaces.
-
Space after keywords (if, for, etc).
-
No trailing space on each line.
-
Spaces are NOT necessary inside brackets.
myFunction(a, b)
-
Use newlines to group logically related pieces of code or to seperate between block definitions.
doSomethingTo(x); doSomethingElseTo(x); andThen(x); nowDoSomethingWith(y); andNowWith(z);
-
-
Indentation
// Four-space, wrap at 80. Works with very long function names, survives // renaming without reindenting, low on space. goog.foo.bar.doThingThatIsVeryDifficultToExplain = function( veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... }; // Four-space, one argument per line. Works with long function names, // survives renaming, and emphasizes each argument. goog.foo.bar.doThingThatIsVeryDifficultToExplain = function( veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... }; // Parenthesis-aligned indentation, wrap at 80. Visually groups arguments, // low on space. function foo(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... } // Parenthesis-aligned, one argument per line. Visually groups and // emphasizes each individual argument. function bar(veryDescriptiveArgumentNumberOne, veryDescriptiveArgumentTwo, tableModelEventHandlerProxy, artichokeDescriptorAdapterIterator) { // ... }
prefix.something.reallyLongFunctionName('whatever', function(a1, a2) { if (a1.equals(a2)) { someOtherLongFunctionName(a1); } else { andNowForSomethingCompletelyDifferent(a2.parrot); } }); var names = prefix.something.myExcellentMapFunction( verboselyNamedCollectionOfItems, function(item) { return item.name; });
someWonderfulHtml = '' + getEvenMoreHtml(someReallyInterestingValues, moreValues, evenMoreParams, 'a duck', true, 72, slightlyMoreMonkeys(0xfff)) + ''; thisIsAVeryLongVariableName = hereIsAnEvenLongerOtherFunctionNameThatWillNotFitOnPrevLine(); thisIsAVeryLongVariableName = 'expressionPartOne' + someMethodThatIsLong() + thisIsAnEvenLongerOtherFunctionNameThatCannotBeIndentedMore(); someValue = this.foo( shortArg, 'Some really long string arg - this is a pretty common case, actually.', shorty2, this.bar()); var IOService = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); if (searchableCollection(allYourStuff).contains(theStuffYouWant) && !ambientNotification.isActive() && (client.isAmbientSupported() || client.alwaysTryAmbientAnyways())) { ambientNotification.activate(); }
-
Binary and Ternary Operators
var x = a ? b : c; // All on one line if it will fit. // Indentation +4 is OK. var y = a ? longButSimpleOperandB : longButSimpleOperandC; // Indenting to the line position of the first operand is also OK. var z = a ? moreComplicatedB : moreComplicatedC;
```JavaScript
var msg = 'This is some HTML';
```
* null
* undefined
* '' the empty string
* 0 the number
But be careful, because these are all true:
* '0' the string
* [] the empty array
* {} the empty object
Instead of this:
```JavaScript
while (x != null) {
```
you can write this:
```JavaScript
while (x) {
```
Instead of this:
```JavaScript
if (y != null && y != '') {
```
you can write this:
```JavaScript
if (y) {
```
-
There are some unintuitive things:
Boolean('0') == true '0' != true 0 != null 0 == [] 0 == false Boolean(null) == false null != true null != false Boolean(undefined) == false undefined != true undefined != false Boolean([]) == true [] != true [] == false Boolean({}) == true {} != true {} != false
-
Ternary Operator
Instead of this:
if (val != 0) { return foo(); } else { return bar(); }
you can write this:
return val ? foo() : bar();
The ternary conditional is also useful when generating HTML:
var html = '<input type="checkbox"' + (isChecked ? ' checked' : '') + (isEnabled ? '' : ' disabled') + ' name="foo">';
-
&& and ||
-
||, the default operator. Instead of this:
/** @param {*=} opt_win */ function foo(opt_win) { var win; if (opt_win) { win = opt_win; } else { win = window; } // ... }
you can write this:
/** @param {*=} opt_win */ function foo(opt_win) { var win = opt_win || window; // ... }
-
"&&" is also useful for shortening code. Instead of this:
if (node) { if (node.kids) { if (node.kids[index]) { foo(node.kids[index]); } } }
you could do this:
if (node && node.kids && node.kids[index]) { foo(node.kids[index]); }
-
-
Use join() to Build Strings It's common to see this:
function listHtml(items) { var html = '<div class="foo">'; for (var i = 0; i < items.length; ++i) { if (i > 0) { html += ', '; } html += itemHtml(items[i]); } html += '</div>'; return html; }
but this is slow in IE, you could better do this:
function listHtml(items) { var html = []; for (var i = 0; i < items.length; ++i) { html[i] = itemHtml(items[i]); } return '<div class="foo">' + html.join(', ') + '</div>'; }
Assigning values to an array is faster than using push().
```JavaScript
function Car(model, year, miles) {
// public attributes
this.model = model;
this.year = year;
this.miles = miles;
}
/*
Note here that we are using Object.prototype.newMethod rather than
Object.prototype so as to avoid redefining the prototype object.
Public methods
*/
Car.prototype.toString = function () {
return this.model + " has done " + this.miles + " miles";
};
var civic = new Car("Honda Civic", 2009, 20000);
var mondeo = new Car("Ford Mondeo", 2010, 5000);
console.log(civic.toString());
```
```JavaScript
function Car(model, year, miles) {
// private members through convention
this._model = model;
this._year = year;
this._miles = miles;
}
Car.prototype._setAttribute(name, value) {
this["_" + name] = value;
};
Car.prototype.setModel = function(model) {
this._setAttribute('model', model);
};
Car.prototype.getModel = function () {
return this._model;
};
```
-
through closure: the downside is that all members are not in the constructor's prototype object. Therefore, each methods are not shared between instances.
function Car(model, year, miles) { // private attributes and methods var _model = model; var _year = year; var _miles = miles; var _setModel = function(value) { // do some checking _model = value; }; // public methods this.getModel = function () { return _model; }; this.setModel = function(model) { _setModel(model); }; }
```JavaScript
var Book = (function () {
var numOfBooks = 0; // Private static attributes.
function checkIsbn(isbn) { // Private static method. ... }
// Return the constructor.
return function(newIsbn, newTitle, newAuthor) {
var isbn; // Private attributes.
this.getIsbn = function () { // Privileged methods.
return isbn;
};
this.setIsbn = function(newIsbn) {
if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.');
isbn = newIsbn;
};
numOfBooks++; // Keep track of how many Books have been created
this.setIsbn(newIsbn);
}
})();
Book.convertToTitleCase = function(inputString) { // Public static method.
...
};
Book.prototype = { // Public, non-privileged methods.
display: function () {
...
}
};
```
```JavaScript
function Employee(name, dept) {
this.name = name || "";
this.dept = dept || "";
}
function Engineer(name, machine) {
// 1. calling the superclass's constructor
Employee.call(this, name, "engineering");
this.machine = machine;
}
// 2. setup the prototype chain
Engineer.prototype = new Employee();
```
```JavaScript
var circleFns = {
area: function () {
return Math.PI * this.radius * this.radius;
},
grow: function () {
this.radius++;
},
shrink: function () {
this.radius--;
}
};
/*
The extend method will simply copy all methods from destination to source.
Here, make sure that method belongs to, not inherited methods, the destination object.
*/
function extend(destination, source) {
for (var k in source) {
if (source.hasOwnProperty(k)) {
destination[k] = source[k];
}
}
return destination;
}
var RoundButton = function(radius, label) {
this.radius = radius;
this.label = label;
};
extend(RoundButton.prototype, circleFns);
extend(RoundButton.prototype, buttonFns);
```
```JavaScript
var someModule = (function () {
//private attributes
var privateVar = 5;
//private methods
var privateMethod = function () {
return 'Private Test';
};
return {
//public attributes
publicVar: 10,
//public methods
publicMethod: function () {
return ' Followed By Public Test ';
},
//let's access the private members
getData: function () {
return privateMethod() + this.publicMethod() + privateVar;
}
}
})(); //the parent here cause the anonymous function to execute and return
someModule.getData();
```
```JavaScript
var myRevealingModule = (function () {
var name = 'John Smith';
var age = 40;
function updatePerson() {
name = 'John Smith Updated';
}
function setPerson() {
name = 'John Smith Set';
}
function getPerson() {
return name;
}
return {
set: setPerson,
get: getPerson
};
})();
// Sample usage:
myRevealingModule.get();
```