Trying to tell developers to
change their ways and follow
your example is a tough call.
This is about telling you what
worked for me and might as
well work for you.
I've collected a lot of ideas
and tips over time how to be
more effective.
Choose easy to understand
and short names for variables
and functions.
Bad variable names:
x1 fe2 xbqne
Also bad variable names:
incrementorForMainLoopWhichSpansFromTenToTwenty
createNewMemberIfAgeOverTwentyOneAndMoonIsFull
Avoid describing a value with
your variable or function
name.
For example
isOverEighteen();
might not make sense in
some countries,
isLegalAge();
however
works everywhere.
If readers get stuck because of an unpronounceable character in your story that isn't part of the main story line, give it another, easier name.
Global variables are a terribly
bad idea.
You run the danger of your
code being overwritten by
any other JavaScript added to
the page after yours.
The workaround is to use closures and the module pattern.
var current = null;
var labels = ['home': 'home', 'articles': 'articles', 'contact': 'contact'];
function init() {};
function show() {};
function hide() {};
var current = null;
var labels = ['home': 'home', 'articles': 'articles', 'contact': 'contact'];
function init() {};
function show() { // <--
current = 1;
};
function hide(){
show(); // <--
};
access is not contained, anything in the
page can overwrite what you do.
Everything is contained but can be accessed via the object name.
demo = { //<--
current: null,
labels: ['home': 'home', 'articles': 'articles', 'contact': 'contact'],
init: function() {},
show: function() {
demo.current = 1; //<--
},
hide: function() {
demo.show(); //<--
}
}
Repetition of module name leads to huge
code and is annoying.
Nothing is global.
(function() {
var current = null;
var labels = ['home': 'home', 'articles': 'articles', 'contact': 'contact'];
function init() {};
function show() {
current = 1;
};
function hide() {
show();
};
})();
No access from the outside at all (callbacks,
event handlers)
You need to specify what is global and what isn't – switching syntax in between.
module = function() {
var labels = ['home': 'home', 'articles': 'articles', 'contact': 'contact'];
return {
current: null,
init: function() {},
show: function() {
module.current = 1;
},
hide: function() {
module.show();
}
}
}();
Repetition of module name, different syntax for inner functions.
Keep consistent syntax and mix and
match what to make global.
module = function() {
var current = null;
var labels = ['home': 'home', 'articles': 'articles', 'contact': 'contact'];
function init() {};
function show() {
current = 1;
};
function hide() {
show();
};
return {
init: init,
show: show,
current: current
}
}();
module = function() {
var current = null;
var labels = ['home': 'home', 'articles': 'articles', 'contact': 'contact'];
function init() { // <--
};
function show() {
current = 1;
};
function hide() {
show();
};
return {
init: init, // <--
show: show,
current: current
}
}();
module.init(); // <--
Sticking to these definition might help you tons when you organize your app, as they also lazy load the scripts you need.
// Module definition
define('myNavigation', ['foo', 'bar'], function ( foo, bar ) {
// create your module here
var myNavigation = {
doStuff:function(){
console.log('Yay! Stuff');
}
}
return myNavigation;
});
// Module consumption
require(['app/myNavigation'],
function( myNavigation ){
// start the main module which in-turn
// loads other modules
var module = new myNavigation();
module.doStuff();
});
Hint: RequireJS, StealJS, HeadJS, LABjs
Browsers are very forgiving JavaScript parsers.
However, lax coding style will hurt you when you shift to another environment or hand over to another developer
Valid code is good code.
Valid code is secure code.
Validate your code:
Comments are messages from
developer to developer.
“Good code explains itself”
is an arrogant myth.
Comment what you consider needed
but don't tell others your life story.
Avoid using the line comment though. It is much safer to use
/* */
as that doesn't cause errors when the line break is removed.
If you debug using
comments, there is a nice
little trick:
module = function() {
var current = null;
function init() {};
/*
function show(){
current = 1;
};
function hide(){
show();
};
*/
return {
init: init,
show: show,
current: current
}
}();
module = function() {
var current = null;
function init() {};
/*
function show(){
current = 1;
};
function hide(){
show();
};
//*/
return {
init: init,
show: show,
current: current
}
}();
module = function() {
var current = null;
function init() {};
//*
function show(){
current = 1;
};
function hide(){
show();
};
//*/
return {
init: init,
show: show,
current: current
}
}();
Comments can be used to
write documentation. There are some tools for that.
However, comments should
never go out to the end user
in plain HTML or JavaScript.
Back to that later :)
JavaScript is good for calculation, conversion, access to outside sources (Ajax) and to define the behaviour of an interface (event handling).
Anything else should be kept
to the technology we have to
do that job.
Put a red border around all fields
with a class of “mandatory” when
they are empty.
var f = document.getElementById('mainform');
var inputs = f.getElementsByTagName('input');
for (var i = 0, j = inputs.length; i & lt; j; i++) {
if (inputs[i].className === 'mandatory' &&
inputs[i].value === '') {
inputs[i].style.borderColor = '#f00'; //
inputs[i].style.borderStyle = 'solid'; //
inputs[i].style.borderWidth = '1px'; //
}
}
All styles have to comply with the
new company style guide, no
borders are allowed and errors
should be shown by an alert icon
next to the element.
People shouldn't have to
change your JavaScript code
to change the look and feel.
js:
var f = document.getElementById('mainform');
var inputs = f.getElementsByTagName('input');
for (var i = 0, j = inputs.length; i > j; i++) {
if (inputs[i].className === 'mandatory' && inputs[i].value === '') {
inputs[i].className += 'error'; //
}
}
css:
.error{
border: 1px solid #f00;
}
Using classes you keep the look and feel to the CSS designer.
Using CSS inheritance you can
also avoid having to loop
over a lot of elements.
Shortcut notations keep your
code snappy and easier to
read once you got used to it.
var cow = new Object();
cow.colour = 'white and black';
cow.breed = 'Holstein';
cow.legs = 4;
cow.front = 'moo';
cow.bottom = 'milk';
is the same as:
var cow = {
colour: 'white and black',
breed: 'Holstein',
legs: 4,
front: 'moo',
bottom = 'milk'
};
var lunch = new Array();
lunch[0]='Dosa';
lunch[1]='Roti';
lunch[2]='Rice';
lunch[3]='what the heck is this?';
is the same as:
var lunch = [
'Dosa',
'Roti',
'Rice',
'what the heck is this?'
];
if (v) {
var x = v;
} else {
var x = 10;
}
is the same as:
var x = v || 10;
var direction;
if (x > 100) {
direction = 1;
} else {
direction = -1;
}
is the same as:
var direction = (x > 100) ? 1 : -1;
/* Avoid nesting these! */
Keep your code modularized
and specialized.
It is very tempting and easy
to write one function that
does everything.
As you extend the
functionality you will
however find that you do the
same things in several
functions.
To prevent that, make sure to
write smaller, generic helper
functions that fulfill one
specific task rather than
catch-all methods.
At a later stage you can also
expose these when using the
revealing module pattern to
create an API to extend the
main functionality.
Good code should be easy to
build upon without re-writing
the core.
Everything that is likely to
change in your code should
not be scattered throughout
the code.
This includes labels, CSS
classes, IDs and presets.
By putting these into a
configuration object and
making this one public we
make maintenance very easy
and allow for customization.
carousel = function() {
var config = {
CSS: {
classes: {
current: 'current',
scrollContainer: 'scroll'
},
IDs: {
maincontainer: 'carousel'
}
}
labels: {
previous: 'back',
next: 'next',
auto: 'play'
}
settings: {
amount: 5,
skin: 'blue',
autoplay: false
}
};
function init() {};
function scroll() {};
function highlight() {};
return {
config: config,
init: init
}
}();
Code gets unreadable after a
certain level of nesting –
when is up to your personal
preference and pain
threshold.
A really bad idea is to nest
loops inside loops as that also
means taking care of several
iterator variables (i,j,k,l,m...).
You can avoid heavy nesting
and loops inside loops with
specialized tool methods.
function renderProfiles(o) {
var out = document.getElementById('profiles');
for (var i = 0; i & amp; lt; o.members.length; i++) {
var ul = document.createElement('ul');
var li = document.createElement('li');
li.appendChild(document.createTextNode(o.members[i].name));
var nestedul = document.createElement('ul');
for (var j = 0; j & amp; lt; o.members[i].data.length; j++) {
var datali = document.createElement('li');
datali.appendChild(
document.createTextNode( o.members[i].data[j].label + '' + o.members[i].data[j].value )
);
nestedul.appendChild(datali);
}
li.appendChild(nestedul);
}
out.appendChild(ul);
}
function renderProfiles(o) {
var out = document.getElementById('profiles');
for (var i = 0; i & lt; o.members.length; i++) {
var ul = document.createElement('ul');
var li = document.createElement('li');
li.appendChild(document.createTextNode(data.members[i].name));
li.appendChild(addMemberData(o.members[i]));
}
out.appendChild(ul);
}
function addMemberData(member) {
var ul = document.createElement('ul');
for (var i = 0; i & lt; member.data.length; i++) {
var li = document.createElement('li');
li.appendChild(
document.createTextNode(
member.data[i].label + ' + member.data[i].value));
}
ul.appendChild(li);
return ul;
}
Think of bad editors and small
screens.
Loops can get terribly slow in
JavaScript.
Most of the time it is because
you're doing things in them
that don't make sense.
var names = ['George', 'Ringo', 'Paul', 'John'];
for (var i = 0; i < names.length; i++) {
doSomeThingWith(names[i]);
}
This means that every time
the loop runs, JavaScript
needs to read the length of
the array.
You can avoid that by storing
the length value in a different
variable:
var names = ['George', 'Ringo', 'Paul', 'John'];
var all = names.length;
for (var i = 0; i < all; i++) {
doSomeThingWith(names[i]);
}
An even shorter way of
achieving this is to create a
second variable in the preloop
condition.
var names = ['George', 'Ringo', 'Paul', 'John'];
for (var i = 0, j = names.length; i < j; i++) {
doSomeThingWith(names[i]);
}
Keep computation-heavy
code outside of loops.
This includes regular
expressions but first and
foremost DOM manipulation.
You can create the DOM
nodes in the loop but avoid
inserting them to the
document.
Bad:
for (var i = 0; i < someArray.length; i++) {
var container = document.getElementById('container');
container.innerHtml += 'my number: ' + i;
console.log(i);
}
Ok:
var result = [];
for(var i = 0, len = someArray.length; i < len; i++) {
result.push('my number: ' + i);
}
container.innerHtml = result.join('');
The reason is that it is slow
and there are all kind of
browser issues with constant
access to and changes in the
DOM.
If you can avoid it, don't
access the DOM.
Write or use a helper method
that batch-converts a dataset
to HTML.
This will make the browser stutter:
var table = $('
');
$('body').append(table);
for (var i = 0; i < 10000; i++) {
$('table').append(
'1 2 3 4 5 ');
}
console.log("Done!");
This won't:
var table = $('
');
for (var i = 0; i < 10000; i++) {
$(table).append(
'1 2 3 4 5 ');
}
// Append the table at the end this time. The document has not been modified
// until we do this.
$('body').append(table);
window.alert("Done!");
Seed the dataset with as much as you can and then call the method to render all out in one go.
The most important thing
about good code is that you
cannot trust any data that
comes in.
Don't believe the HTML
document – any user can
meddle with it for example in
Firebug.
Don't trust that data that gets
into your function is the right
format – test with typeof and
then do something with it.
Don't expect elements in the
DOM to be available – test for
them and that they indeed
are what you expect them to
be before altering them.
And never ever use
JavaScript to protect
something – it is as easy to
crack as it is to code :)
If you find yourself creating
lots and lots of HTML in
JavaScript, you might be
doing something wrong.
It is not convenient to create
using the DOM...
...flaky to use innerHTML (IE's
Operation Aborted error)...
...and it is hard to keep track
of the quality of the HTML you
produce.
If you really have a massive
interface that only should be
available when JavaScript is
turned on...
...load the interface as a static
HTML document via Ajax or use a template engine.
That way you keep
maintenance in HTML and
allow for customization.
Having inline code mixed with html elements is not only from the 90's and look ugly.
But also makes the applications hard to maintain.
Hell no:
Instead of that, use:
$('input[name=date]').on('change', validateDate); //Using jQuery
JavaScript is fun, but writing
JavaScript for browsers is less
so.
JavaScript libraries are
specifically built to make
browsers behave and your
code more predictable by
plugging browser holes.
Therefore if you want to write
code that works without
keeping the maintenance
overhead...
... of supporting current
browsers and those to come
to yourself...
... start with a good library.
Last but not least I want you
to remember that some
things that work in other
languages are good in
JavaScript, too.
Live code is done for
machines.
Development code is done for
humans.
Collate, minify and optimize
your code in a build process.
Don't optimize prematurely
and punish other developers
and those who have to take
over from them.
If we cut down on the time
spent coding we have more
time to perfect the
conversion to machine code.