I’ve been writing JavaScript code for much longer
than I care to remember. I am very excited about the language’s recent
success; it’s good to be a part of that success story. I’ve written
dozens of articles, book chapters and one full book on the matter, and
yet I keep finding new things. Here are some of the “aha!” moments I’ve
had in the past, which you can try out rather than waiting for them to
come to you by chance.
[By the way, did you know we have a brand
new free Smashing
Email Newsletter? Subscribe now and get fresh short tips and tricks
on Tuesdays!]
Shortcut Notations
One of the things I love
most about JavaScript now is shortcut notations to generate objects and
arrays. So, in the past when we wanted to create an object, we wrote:
1 |
var car = new Object(); |
4 |
car.hubcaps = 'spinning' ; |
The
same can be achieved with:
Much
shorter, and you don’t need to repeat the name of the object. Right
now, car
is fine, but what happens when you use invalidUserInSession
?
The main gotcha in this notation is IE. Never ever leave a trailing
comma before the closing curly brace or you’ll be in trouble.
The
other handy shortcut notation is for arrays. The old school way of
defining arrays was this:
1 |
var moviesThatNeedBetterWriters
= new Array( |
2 |
'Transformers' , 'Transformers2' , 'Avatar' , 'Indiana Jones 4' |
The
shorter version of this is:
1 |
var moviesThatNeedBetterWriters
= [ |
2 |
'Transformers' , 'Transformers2' , 'Avatar' , 'Indiana
Jones 4' |
The
other thing about arrays is that there is no such thing as an
associative array. You will find a lot of code examples that define the
above car
example like so:
4 |
car[ 'hubcaps' ] = 'spinning' ; |
This
is not Sparta; this is madness—don’t bother with this. “Associative
arrays” is a confusing name for objects.
Another very cool
shortcut notation is the ternary notation for conditions. So, instead of
the following…
…
You could write a shorter version using the ternary notation:
1 |
var direction
= x < 200 ? 1 : -1; |
The
true
case of the condition is after the question mark, and
the other case follows the colon.
JSON As A Data Format
Before
I discovered JSON to store data, I did all kinds of crazy things to put
content in a JavaScript-ready format: arrays, strings with control
characters to split, and other abominations. The creation of JSON by Douglas Crockford changed all that.
Using JSON, you can store complex data in a format that is native to
JavaScript and doesn't need any extra conversion to be used immediately.
JSON
is short for "JavaScript Object Notation" and uses both of the
shortcuts we covered earlier.
So, if I wanted to describe a band,
for example, I could do the following:
02 |
"name" : "The Red Hot Chili Peppers" , |
05 |
"name" : "Anthony
Kiedis" , |
09 |
"name" : "Michael 'Flea' Balzary" , |
10 |
"role" : "bass guitar, trumpet, backing vocals" |
14 |
"role" : "drums,percussion" |
17 |
"name" : "John Frusciante" , |
You
can use JSON directly in JavaScript and, when wrapped in a function
call, even as a return format of APIs. This is called JSON-P and is
supported by a lot of APIs out there. You can use a data endpoint,
returning JSON-P directly in a script node:
01 |
<div id= "delicious" ></div><script> |
02 |
function delicious(o){ |
04 |
for ( var i=0;i<o.length;i++){ |
05 |
out += '<li><a
href="' + o[i].u + '">' + |
09 |
document.getElementById( 'delicious' ).innerHTML = out; |
This
calls the Delicious Web service to get my latest JavaScript bookmarks
in JSON format and then displays them as an unordered list.
In
essence, JSON is probably the most lightweight way of describing complex
data—and it runs in a browser. You can even use it in PHP using the json_decode()
function.
Native JavaScript Functions (Math, Array And String)
One
thing that amazed me is how much easier my life got once I read up
thoroughly on the math and string functions of JavaScript. You can use
these to avoid a lot of looping and conditions. For example, when I had
the task of finding the largest number in an array of numbers, I used to
write a loop, like so:
1 |
var numbers =
[3,342,23,22,124]; |
3 |
for ( var i=0;i<numbers.length;i++){ |
This
can be achieved without a loop:
1 |
var numbers =
[3,342,23,22,124]; |
2 |
numbers.sort( function (a,b){ return b -
a}); |
Notice
that you cannot use sort()
on a number array because it
sorts lexically. There's a good
tutorial on sort()
here in case you need to know more.
Another
interesting method is Math.max()
. This one returns the
largest number from a list of parameters:
1 |
Math.max(12,123,3,2,433,4); |
Because
this tests for numbers and returns the largest one, you can use it to
test for browser support of certain properties:
1 |
var scrollTop=
Math.max( |
2 |
doc.documentElement.scrollTop, |
This
works around an Internet Explorer problem. You can read out the scrollTop
of the current document, but depending on the DOCTYPE
of
the document, one or the other property is assigned the value. When you
use Math.max()
you get the right number because only one of
the properties returns one; the other will be undefined
.
You can read more about shortening JavaScript with math functions here.
Other
very powerful functions to manipulate strings are split()
and join()
. Probably the most powerful example of this is
writing a function to attach CSS classes to elements.
The thing
is, when you add a class to a DOM element, you want to add it either as
the first class or to already existing classes with a space in front of
it. When you remove classes, you also need to remove the spaces (which
was much more important in the past when some browsers failed to apply
classes with trailing spaces).
So, the original function would be
something like:
1 |
function addclass(elm,newclass){ |
3 |
elm.className = (c === '' ) ? newclass : c+ ' ' +newclass; |
You
can automate this using the split()
and join()
methods:
1 |
function addclass(elm,newclass){ |
2 |
var classes =
elm.className.split( ' ' ); |
3 |
classes.push(newclass); |
4 |
elm.className = classes.join( '
' ); |
This
automatically ensures that classes are space-separated and that yours
gets tacked on at the end.
Event Delegation
Events make
Web apps work. I love events, especially custom events, which make your
products extensible without your needing to touch the core code. The
main problem (and actually one of its strengths) is that events are
removed from the HTML—you apply an event listener to a certain element
and then it becomes active. Nothing in the HTML indicates that this is
the case though. Take this abstraction issue (which is hard for
beginners to wrap their heads around) and the fact that "browsers" such
as IE6 have all kind of memory problems and too many events applied to
them, and you'll see that not using too many event handlers in a
document is wise.
This is where event delegation
comes in. When an event happens on a certain element and on all the
elements above it in the DOM hierarchy, you can simplify your event
handling by using a single handler on a parent element, rather than
using a lot of handlers.
What do I mean by that? Say you want a
list of links, and you want to call a function rather than load the
links. The HTML would be:
1 |
<h2>Great Web resources</h2> |
3 |
<li><a href= "http:///wsc" >Opera Web Standards
Curriculum</a></li> |
4 |
<li><a href= "http://" >Sitepoint</a></li> |
5 |
<li><a href= "http://" >A List Apart</a></li> |
6 |
<li><a href= "http://" >YUI Blog</a></li> |
7 |
<li><a href= "http://" >Blame it on the voices</a></li> |
8 |
<li><a href= "http://" >Oddly specific</a></li> |
The
normal way to apply event handlers here would be to loop through the
links:
03 |
var resources =
document.getElementById( 'resources' ); |
04 |
var links =
resources.getElementsByTagName( 'a' ); |
05 |
var all = links.length; |
06 |
for ( var i=0;i<all;i++){ |
08 |
links[i].addEventListener( 'click' ,handler, false ); |
This
could also be done with a single event handler:
02 |
var resources =
document.getElementById( 'resources' ); |
03 |
resources.addEventListener( 'click' ,handler, false ); |
06 |
if (x.nodeName.toLowerCase()
=== 'a' ){ |
07 |
alert( 'Event delegation:' + x); |
Because
the click happens on all the elements in the list, all you need to do
is compare the nodeName
to the right element that you want
to react to the event.
Disclaimer: while both of the event
examples above work in browsers, they fail in IE6. For IE6, you need to
apply an event model other than the W3C one, and this is why we use
libraries for these tricks.
The benefits of this approach are more
than just being able to use a single event handler. Say, for example,
you want to add more links dynamically to this list. With event
delegation, there is no need to change anything; with simple event
handling, you would have to reassign handlers and re-loop the list.
Anonymous
Functions And The Module Pattern
One of the most annoying things
about JavaScript is that it has no scope for variables. Any variable,
function, array or object you define that is not inside another function
is global, which means that other scripts on the same page can
access—and will usually override— them.
The workaround is to
encapsulate your variables in an anonymous function and call that
function immediately after you define it. For example, the following
definition would result in three global variables and two global
functions:
4 |
function createMember(){ |
7 |
function getMemberDetails(){ |
Any
other script on the page that has a variable named status
could cause trouble. If we wrap all of this in a name such as myApplication
,
then we work around that issue:
01 |
var myApplication
= function (){ |
04 |
var status = 'single' ; |
05 |
function createMember(){ |
08 |
function getMemberDetails(){ |
This,
however, doesn't do anything outside of that function. If this is what
you need, then great. You may as well discard the name then:
04 |
var status = 'single' ; |
05 |
function createMember(){ |
08 |
function getMemberDetails(){ |
If
you need to make some of the things reachable to the outside, then you
need to change this. In order to reach createMember()
or getMemberDetails()
,
you need to return them to the outside world to make them properties of
myApplication
:
01 |
var myApplication
= function (){ |
04 |
var status = 'single' ; |
06 |
createMember: function (){ |
09 |
getMemberDetails: function (){ |
This
is called a module pattern or singleton. It was mentioned a lot by
Douglas Crockford and is used very much in the Yahoo User Interface Library YUI.
What ails me about this is that I need to switch syntaxes to make
functions or variables available to the outside world. Furthermore, if I
want to call one method from another, I have to call it preceded by the
myApplication
name. So instead, I prefer simply to return
pointers to the elements that I want to make public. This even allows me
to shorten the names for outside use:
01 |
var myApplication
= function (){ |
04 |
var status = 'single' ; |
05 |
function createMember(){ |
08 |
function getMemberDetails(){ |
I've
called this "revealing
module pattern."
Allowing For Configuration
Whenever
I've written JavaScript and given it to the world, people have changed
it, usually when they wanted it to do things that it couldn't do out of
the box—but also often because I made it too hard for people to change
things.
The workaround is to add configuration objects to your
scripts. I've written
about JavaScript configuration objects in detail, but here's the
gist:
- Have an object as part of your whole script called
configuration
.
- In
it, store all of the things that people will likely change when they
use your script:
- CSS ID and class names;
- Strings (such as
labels) for generated buttons;
- Values such as "number of images
being displayed," "dimensions of map";
- Location, locale and
language settings.
- Return the object as a public
property so that people can override it.
Most of the time
you can do this as a last step in the coding process. I've put together
an example in "Five
things to do to a script before handing it over to the next developer."
In
essence, you want to make it easy for people to use your code and alter
it to their needs. If you do that, you are much less likely to get
confusing emails from people who complain about your scripts and refer
to changes that someone else actually did.
Interacting With The
Back End
One of the main things I learned from all my years with
JavaScript is that it is a great language with which to make interactive
interfaces, but when it comes to crunching numbers and accessing data
sources, it can be daunting.
Originally, I learned JavaScript to
replace Perl because I was sick of copying things to a cgi-bin
folder in order to make it work. Later on, I learned that making a
back-end language do the main data churning for me, instead of trying to
do all in JavaScript, makes more sense with regard to security and
language.
If I access a Web service, I could get JSON-P as the
returned format and do a lot of data conversion on the client, but why
should I when I have a server that has a richer way of converting data
and that can return the data as JSON or HTML… and cache it for me to
boot?
So, if you want to use AJAX, learn about HTTP and about
writing your own caching and conversion proxy. You will save a lot of
time and nerves in the long run.
Browser-Specific Code Is A Waste
Of Time. Use Libraries!
When I started Web development, the
battle between using document.all
and using document.layers
as the main way to access the document was still raging. I chose document.layers
because I liked the idea of any layer being its own document (and I had
written more than enough document.write
solutions to last a
lifetime). The layer model failed, but so did document.all
.
When Netscape 6 went all out supporting only the W3C DOM model, I loved
it, but end users didn't care. End users just saw that this browser
didn't show the majority of the Internets correctly (although it
did)—the code we produced was what was wrong. We built short-sighted
code that supported a state-of-the-art environment, and the funny thing
about the state of the art is that it is constantly changing.
I've
wasted quite some time learning the ins and outs of all of the browsers
and working around their issues. Doing this back then secured my career
and ensured that I had a great job. But we shouldn't have to go through
this trial by fire any longer.
Libraries such as YUI, jQuery and
Dojo are here to help us with this. They take on the problems of
browsers by abstracting the pains of poor implementation,
inconsistencies and flat-out bugs, and relieve us of the chore. Unless
you want to beta test a certain browser because you're a big fan, don't
fix browser issues in your JavaScript solutions, because you are
unlikely to ever update the code to remove this fix. All you would be
doing is adding to the already massive pile of outdated code on the Web.
That
said, relying solely on libraries for your core skill is short-sighted.
Read up on JavaScript, watch some good videos and tutorials on it, and
understand the language. (Tip: closures are God's gift to the JavaScript
developer.) Libraries will help you build things quickly, but if you
assign a lot of events and effects and need to add a class to every HTML
element in the document, then you are doing it wrong.
Resources
In
addition to the resources mentioned in this article, also check out the
following to learn more about JavaScript itself: