First and formemost, Javascript must be treated like other important source in a project.
In some languages, it's clear what's supposed to go in a single file. In Java, for instance, one file = one public class. Javascript does not make it so easy. Some of the factors to consider include:
Similar advice as in other languages:
i
, j
, etc.camelCaseNames
PascalCaseNames
ALL_CAPS_NAMES
Remember that functions are much more central to Javascript than to C# or to Java. More on that later.
Just keep things readable.
There are plenty of good unit test frameworks available, and they're easy to use:
And there are many more
User interfaces are hard to test.
There is no way around that. But there are tools to make is somewhat easier:
But one good alternative for testing general application flows, so long as you don't need to worry about specific browser bugs, is the headless Webkit implementation, Phantom, hooked together with any of the unit test suites.
JSLint is a (very intentionally) opinionated piece of software. It comes with the warning
JSLint will hurt your feelings
. It helps enforce many of the syntactic best practices here. Do not assume
it's the last word. Your project can choose to override its default settings without fear. But be sure that you understand
what the settings are for, and why you're doing so.
JSHint is a more friendly, more configurable, linter. It might be worth using if you find yourself cursing Douglas Crockford every day.
The most important thing is to make running these part of your regular routine.
Javascript is not an Object-Oriented language.*
Javascript is not an Functional language.
It is a mixture of both, and although it has plenty of warts, it takes many of the best parts of OO and the best parts of Functional and combines them into its own mix.
* "Advocating Object-Oriented Programming is like advocating Pants-Oriented Clothing." – Steve Yegge
public interface Behavior {
public int moveCommand();
}
public class AggressiveBehavior implements Behavior{
public int moveCommand() {
System.out.println("Attack!");
return 1;
}
}
public class DefensiveBehavior implements Behavior{
public int moveCommand() {
System.out.println("Run Away!");
return -1;
}
}
public class NormalBehavior implements Behavior{
public int moveCommand() {
System.out.println("Hang around.");
return 0;
}
}
// ...
robot.setBehavior(new NormalBehavior());
var agressiveBehaviour = function() {
console.log("Attack!");
return 1;
};
var defensiveBehavior = function() {
console.log("Run Away!");
return -1;
}
var normalBehavior = function() {
console.log("Hang around.");
return 0;
}
// ...
robot.setBehavior(normalBehavior);
This section describes some of the simpler things that you might have to do a bit differently in Javascript than in other languages. We will talk about:
For performance reasons, it's best
to place all your Javascript <SCRIPT>
tags at the bottom of the page.
If you do need to include them in the HEAD
, it's probably worth running them on page load or document ready.
Consider using an AMD-loader such as Require.js. These tools make it substantially easier to manage dependencies and to keep the global namespace clean. It also makes various more organizational techniqes such as namespacing unnecessary.
Javascript does not (yet) have a native module feature. But plain objects can easily serve that role. If you can't use an AMD-loader, then a technique like this is probably your best bet:
var myApp = myApp || {};
myApp.util = myApp.util || {};
myApp.util.function1 = { ... };
myApp.util.function2 = { ... };
You might need to declare and intialize your namespace at the top of each file that uses it. Alternately, you can write a function that does this for you:
myApp.namespace("util"); // or
namespace("myApp.util");
But then you'll need to ensure that the namespace
function is definitely in place before this code runs.
While the core language of Javascript has sped up many-fold in recent years and can now be used for some surprisingly performance-intensive work, access to the Document Object Model (DOM) has not kept up.
It's still slow.
Very slow.
Very, very slow.
To mitigate this, your best bet is to minimize the number of queries you make to the DOM, to cache the results of these queries where feasible, to store data in places other than the DOM when possible.
There is a significant problem with this code *
var links = document.getElementsByTagName("A");
for (var i = 0, len = links.length; i < len; i++) {
links[i].addEventListener("click", function(event) {
// do something
}
}
This creates an event listener for every single <A>
element on the page, each with its own copy of an
identical function. This is often a huge waste of memory.
Try something like this instead:
document.body.addEventListener("click", function(event) {
if (event.target && event.target.nodeName === "A") {
// do something
}
}
This accomplishes exactly the same thing with a single listener.
*besides the fact that it won't work in IE
Because Javascript is delivered over the wire in source code format, brevity actually matters. But with compressors often doing much of this job for you, often the only issue left is to not create outrageously long variable names. Other than that, using these formatting conventions will help with legibility and maintainability. But in some cases, they make the difference between code that works and code that doesn't.
if
` and the corresponding `(
` to set them apart from method invocation
where the parenthesis should be adjacent to the function{
` at the end of the line===
` and `!==
` (as opposed to `==
` and `!=
`) to
avoid unintended type coercionThere are some features of Javascript that seem perversely designed to catch the unwary. Some are probably poor design; others simply have to do with the language being substantially different than languages that superficially look much like it.
Javascript does not have block scope. All variables are scoped to the nearest containing function, or are global. In upcoming
versions, this will change, and there will be a `let
` construct parallel to the `var
` one, that does
allow for block scoping, but for now, examine this:
function someFunction(param) {
if (param > 10) {
var x = 42;
}
for (var i = 0; i < 10; i++) {
localFunction(param, i, x);
}
// QUESTION: What's in scope here?
function localFunction() {
// ...
}
}
Question: What's in scope at the marked spot?
Answer: param
, x
, i
, localFunction
,
plus anything in the global scope.
Probably one of the worst misfeatures of Javascript is how easy it is to accidentally create global variables.
function party() {
var Event = "Birthday Party", Where = "Chucky Cheese", Time = "1:00";
Date = "Friday", RSVP = "800-555-1414", GuestOfHonor = "Tommy";
schedule(Event, Where, Time, Date, RSVP, GuestOfHonor);
}
Do you see the bug?
It's the first semicolon. Programmers generally know that global variables are a bad idea. In Javascript, if you use a
var
statement in a global context, or forget to use a var
statement altogether,
your variable is now a global. It can clobber almost any other global out there, including built-in things such
as the Date constructor.
Moral: be careful of your var
statements.
Another moral: Think carefully about using commas to separate your var
statements. It can be fragile.
Javascript offers two styles of equality/inequality testing. One of them (`==
` / `!=
`) coerces the
objects to be of similar types and then checks for equality. The other (`===
` / `!==
`) does no
coercion, comparing both type and value.
Although there are theoretically some times when the coercion could be helpful and exactly what you want, and there are times when you know for certain that the types under comparison are the same, and so the first style would work, you will run into problems with some linters, and will likely run into maintenance problems as other developors may not recognize what you know.
The recommendation is that if you need the type coercion, perform it manually, and test with `===
` or `!==
`:
// Don't do this to force string `amt` to a number:
if (amt != 0) { /* ... */}
// Instead, be explicit:
if (Number(amt) !== 0) { /* ... */}
It sounds like a helpful idea: if you forget a semicolon, the interpreter can generally figure out that you meant to include one.
However…
var expand = function(rect) {
return
{
height: rect.height * 2,
width: rect.width * 2
};
};
var myRect = {height: 10, width: 20};
alert(expand(myRect).width);
Question: What does this return?
Question: What does this return?
var expand = function(rect) {
return
{
height: rect.height * 2,
width: rect.width * 2
};
};
var myRect = {height: 10, width: 20};
alert(expand(myRect).width);
Answer: Nothing. It's a syntax error. The `return
` on it's own line is considered a statement of
its own, and a virtual semicolon is inserted. The `{
` opens a block, and `height:
` is a label, but then
because of the comma, the statement is not complete before another label-like token, `width:
` is encountered, and we
hit an error.
Had the `{
` been on the same line as the `return
`, we would have had no issues. This is the reason for
insisting on K & R-style brace placement. But this whole thing is a reason to be wary of Automatic Semicolon Inserion.
When testing for truthiness, such as in an `if
` test, Javascript has a number of distinct values that report as false:
0
(both positive and negative zero [don't ask!]),
NaN
,
""
(empty string),
false
,
null
, and
undefined
.
Anything else is truthy, including some surprising ones such as:
"0"
,
{}
(empty object), and
[]
(empty array).
So instead of something like this to test an object with a string property:
if (obj !== null && obj !== undefined && obj.prop !== null &
obj.prop !== undefined && obj.prop != "") { /* ... */ }
You can simply do something like this:
if (obj && obj.prop) { /* ... */ }
In Object-Oriented languages, it's clearly Objects (and Classes) that are the central features.
Javascript has three central features that work closely together:
To use the language fully, we must be able to take advantage of how all of these work and how they interact.
First of all, start simple. It might always be enough to do something like this:
var dog = {
name: "Rover"
speak: function() {alert(this.name + " says woof";}
};
But if you have many similar objects, you might want to reuse the speak
function. That's where prototypes come in.
If you need to start sharing prototypes, you can do something like this:
var dogPrototype = {
speak: function() {alert(this.name + " says woof");}
};
var dog1 = Object.create(dogPrototype);
dog1.name = "Rover";
dog1.speak();
// or
var dog2 = Object.create(dogPrototype, {name: {value: "Fido"}});
dog2.speak();
Note that this technique requires a shim to work in older versions of IE
You can also do something that feels more familiar:
var Dog = function(name) {
this.name = name;
};
Dog.prototype.speak = function() {alert(this.name + " says woof");}
var dog1 = new Dog("Rover");
dog1.speak();
While not exactly the same, this has a very similar effect as the previous sample.
Developers should become familiar with JavaScript's prototypal inheritance.
Unlike Java or C#, JavaScript does not have classes.
Prototypal inheritance is
powerful but misunderstood — essentially, JavaScript objects contain a pointer
to the parent object. On property access, the Javascript engine walks "up the
tree" to find the requested property (arriving at undefined
should the
property not exist anywhere in the chain).
Functions are first-class citizens of the Javascript world, at least as important as objects, and often more important.
Using functions in these ways is essential to properly understanding Javascript.
var isPreexisting = function(vehId) {
if (typeof vehId !== "string") {
vehId = (vehId && vehId["@id"]) || "Unknown";
}
return (Model.get("actionCode", {vehId: vehId}) !== "A");
};
var isNonOwned = function(vehId) {
if (typeof vehId !== "string") {
vehId = (vehId && vehId["@id"]) || "Unknown";
}
return (Model.get("vehicleType", {vehId: vehId}) === "NO");
};
var isCompOnly = function(pkgId, vehId) {
if (typeof vehId !== "string") {
vehId = (vehId && vehId["@id"]) || "Unknown";
}
return !!Model.get("compOnly", {vehId: vehId, pkgId: pkgId});
};
All these Strategic UI functions should accept either a vehicleId as a String or a vehicle object with a string property named "@id". But there is too much repetetive code in them.
var getRealVehicleId = function(vehId) {
return (typeof vehId === "string") ? vehId :
(vehId && vehId["@id"]) || "Unknown";
};
var isPreexisting = function(vehId) {
vehId = getRealVehicleId(vehId);
return (Model.get("actionCode", {vehId: vehId}) !== "A");
};
var isNonOwned = function(vehId) {
vehId = getRealVehicleId(vehId);
return (Model.get("vehicleType", {vehId: vehId}) === "NO");
};
var isCompOnly = function(pkgId, vehId) {
vehId = getRealVehicleId(vehId);
return !!Model.get("compOnly", {vehId: vehId, pkgId: pkgId});
};
This style refactoring should look familiar. There is nothing wrong with it. But Javascript offers alternative techniques that can be more powerful, and, once learned, more intuitive
var withVehId = paramReplacer(function(vehId) {
return (typeof vehId === "string") ? vehId :
(vehId && vehId["@id"]) || "Unknown";
});
var isPreexisting = withVehId(function(vehId) {
return (Model.get("actionCode", {vehId: vehId}) !== "A");
});
var isNonOwned = withVehId(function(vehId) {
return (Model.get("vehicleType", {vehId: vehId}) === "NO");
});
var isCompOnly = withVehId(function(pkgId, vehId) {
return !!Model.get("compOnly", {vehId: vehId, pkgId: pkgId});
}, 1);
Instead of adding code inside our simple functions, we wrap them up inside another function
that will do the translations of the parameter before calling our function. But this depends
on paramReplacer
. Let's look at that.
var paramReplacer = function (adjuster) {
return function(fn, index) {
index = (typeof index === "number") ? index : 0;
return function() {
if (arguments.length > index) {
return fn.apply(this, [].splice.call(arguments,
index, 1, adjuster(arguments[index]));
}
return fn.apply(this, arguments);
};
};
};
Okay, that's a mouthful. But this sort of technique can be very powerful. This is a function that returns functions that return functions. Using Javascript this way in a few key places can significantly simplify the rest of your code.
var withVehId = paramReplacer(function(vehId) {
return (typeof vehId === "string") ? vehId :
(vehId && vehId["@id"]) || "Unknown";
});
var isCompOnly = withVehId(function(pkgId, vehId) {
return !!Model.get("compOnly", {vehId: vehId, pkgId: pkgId});
}, 1);
A common technique seen in Javascript program is the Immediately Invoked Function Expression (IIFE), which looks like this:
(function() {
// something here
}());
This creates an anonymous function, and immediately invokes it. Note that you can use parameters as well, as in this common technique to
ensure that the `$
` variable is assigned to jQuery
inside your function:
(function($) {
// here $ is always a reference to jQuery
}(jQuery));
Is there something surprising about this?:
function createHandler(key) {
var count;
return function() {
alert("You called handler " + key + " " + (++count) + "time(s)");
}
}
var myFunc1 = createHandler("abc");
var myFunc2 = createHandler("def");
myFunc1(); // alerts "You called handler abc 1 time(s)"
myFunc1(); // alerts "You called handler abc 2 time(s)"
myFunc2(); // alerts "You called handler def 1 time(s)"
There is something that might be surprising about this:
function createHandler(key) {
var count;
return function() {
alert("You called handler " + key + " " + (++count) + "time(s)");
}
}
var myFunc1 = createHandler("abc");
var myFunc2 = createHandler("def");
myFunc1(); // alerts "You called handler abc 1 time(s)"
myFunc1(); // alerts "You called handler abc 2 time(s)"
myFunc2(); // alerts "You called handler def 1 time(s)"
When the createClickHandler
function returns a new function, it seems as
though key
and count
should go out of scope. Instead JavaScript
creates a closure of all variables in scope. myFunc
has
access to (and can change) those values when it's called.
A closure
is simply a scope that is kept around after you
would otherwise expect it to be gone. A function defined with access to that scope retains
access.
An example from Webreference shows how we can extend the
document.createElement
function to accept additional parameters such as id and classname:
document.createElement = (function (fn) {
return function (type, id, className) {
var elem = fn.call(document, type);
if (id) elem.id = id;
if (className) elem.className = className;
return elem;
};
})(document.createElement);
Notice the use of an IIFE here. They are commonly used to create closures.
Note: Replacing the native DOM method might not be a great idea, by the way.
var nextId = (function() {
var ctr = 0;
return function() {return ctr++;};
}());
Note that this although the ctr
variable is not directly visible outside that function,
it stays alive, and the function can modify its value. This differs from some languages which
offer closure-access to static variables, but do not allow you to change them.
Again, this uses a simple IIFE.
var rows = myTable.getElementsByTagName("TR");
for (var i = 0; i < rows.length; i++) {
rows[i].addEventListener("click", function() {
alert("Clicked row " + (i + 1)};
});
}
This looks as though it will alert the number of the current row on a click.
In fact, though, it alerts the final value of i (one more than the number of rows.)
var rows = myTable.getElementsByTagName("TR");
for (var i = 0; i < rows.length; i++) {
rows[i].addEventListener("click", (function(row) {return function() {
alert("Clicked row " + row)};
}(i + 1)));
}
There is much complex code when asynchronous actions are involved. Often we have deeply nested callbacks to handle the results of many asynchronous calls. The code becomes ugly and brittle. To fix this, some libraries have introduced a Promises API. A promise is not exactly a value. It's an agreement to call certain registered functions with a value when that value is ready or certain other ones if an error prevents determination of that value. jQuery implemented its version of this API in the 1.5.x branch. And we've started to use it in various places in our application.
There is still some confusion over the various proposed specs for Promises, but at the base
is a very simple function, then(callback, errback)
. An object that has such a function
and handles these input parameters properly is a Promise. An object that implements this
can be returned from a function, and that function does not have to call your callback
functions directly, leaving this responsibility entirely in the hands of the caller.
You can attach a success callback and/or an error callback at any point in the lifecycle of the asynchronous operation. If the operation hasn't finished it will add these to a queue to process later. If it's already finished, the success or failure callback will be handled immediately.
Another function, when
allows us to abstract out the difference between synchronous and
asynchronous behaviors. when
is supplied an object, which might or might not be a Promise,
and returns a Promise whose success and failure handlers will be called when the input
promise is resolved or rejected.
var pause = function(millis) {
var dfd = jQuery.Deferred();
setTimeout(function() {
dfd.resolve(new Date());
}, millis || 1000);
return dfd.promise();
}
alert("The time is now " + new Date());
pause(5000).then(function(now) {
alert("Five seconds later the date is " + now);
});
Often it's easier to use this version, which does the same thing:
var pause = function(millis) {
return jQuery.Deferred(function(dfd) {
setTimeout(function() {
dfd.resolve(new Date());
}, millis || 1000);
}).promise();
}
The interaction of simple prototype-based objects, first-class functions, and mutable closures gives us many more ways to structure our code than do simple OO languages. So sometimes additional guidelines are needed.
Note that this advice is all conditional. There is no single best style of Javascript application. There are a lot of powerful tools available; you will have to choose which ones are most important to your project.
Comments
Really no different from other languages.
Again, for documentation comments you have more choices than you might have for some languages, but there are many goods ones, and there is no clear consensus about the best one. JSDoc, which languished for years is moving quickly again and starting to look good, though.
(My personal opinion is that most code commenting ends up contributing very little to a project. I find it useful to comment tricky bits that aren't immediately obvious. But if you have those, you should probably come back and fix it anyway.)