There have been a number of articles published over the years that
discuss best practices techniques for JavaScript. I thought I would go
a little bit beyond the scope of those articles and outline a number of
advanced techniques and practices that I have personally used or read
about that could be invaluable in certain circumstances.
This article doesn't necessarily cover every detail of the methods
I'm describing, but provides an overview, along with code examples, of
some practical JavaScript coding techniques.
1. Closures to Extend Variable Scope
Closures in JavaScript are a fairly straightforward concept, and
have been discussed online in a number of in-depth articles. The fact
that they are straightforward doesn't necessarily mean they're simple
however, as seen by the extensive articles that cover the subject.
Simply put, closures allow variable scope to be extended past the
common scope restrictions of functions. I like the way Jeremy Keith
describes closures in his book Bulletproof Ajax:
"Think of closures as a kind of regional scope: broader than local but not as broad as global."
To create a closure, you nest a function inside of a function. That
inner function has access to all variables in its parent function's
scope. This comes in handy when creating methods and properties in
object oriented scripts. Here is a simple example that demonstrates the
use of a closure:
function myObject() {
this.property1 = "value1";
this.property2 = "value2";
var newValue = this.property1;
this.performMethod = function() {
myMethodValue = newValue;
return myMethodValue;
};
}
var myObjectInstance = new myObject();
alert(myObjectInstance.performMethod());
The key portions of the script are the nested anonymous function are highlighted in green and the method call in the alert
function (last line). Because the method in the alert is actually
calling a nested function, that method is able to read the value of the
variable called newValue , even thought that variable is not within the scope of the anonymous function, or method.
Developers use closures all the time, probably unknowingly, since a
closure is created any time an anonymous function is nested inside
another function and utilizes variables from the parent function's
scope. The power of the closure is revealed when that method (the inner
function) is called, and values that normally wouldn't be accessible
are within "regional" scope and are thus able to be used as any other
value.
See the references below for some deeper explanations of closures
and their relation to scope. I also highly recommend you pick up a good
advanced JavaScript book that offers a good discussion of the concepts
associated with closures.
Further Reading
2. Object Literals to Pass Optional Arguments
Here is a handy coding tip to keep in mind when dealing with
functions that can accept a large number of optional arguments. Instead
of passing the large number of arguments in the conventional fashion,
which could unnecessarily complicate the function, you can pass just
one argument which ends up being a collection of arguments declared in
an object literal.
Let's look, first of all, at how we might do this in the typical manner, so we can see the contrast:
function showStatistics(name, team, position, average, homeruns, rbi) {
document.write("<p><strong>Name:</strong> " + arguments[0] + "<br />");
document.write("<strong>Team:</strong> " + arguments[1] + "<br />");
if (typeof arguments[2] === "string") {
document.write("<strong>Position:</strong> " + position + "<br />");
}
if (typeof arguments[3] === "number") {
document.write("<strong>Batting Average:</strong> " + average + "<br />");
}
if (typeof arguments[4] === "number") {
document.write("<strong>Home Runs:</strong> " + homeruns + "<br />");
}
if (typeof arguments[5] === "number") {
document.write("<strong>Runs Batted In:</strong> " + rbi + "</p>");
}
}
showStatistics("Mark Teixeira");
showStatistics("Mark Teixeira", "New York Yankees");
showStatistics("Mark Teixeira", "New York Yankees", "1st Base", .284, 32, 101);
The function above can take up to 6 arguments. The first two
arguments are mandatory, so inside the function, we don't check for
their existence. The last 4 arguments are not mandatory, so we only
display their values if they exist.
We call the function 3 different times (last 3 lines), with
different numbers of arguments each time. You can see that if the
number of passed arguments was in the dozens, or more, the code could
look a little messy, and would be harder to maintain, or read.
Now let's look at the same code using object literals to pass the arguments:
function showStatistics(args) {
document.write("<p><strong>Name:</strong> " + args.name + "<br />");
document.write("<strong>Team:</strong> " + args.team + "<br />");
if (typeof args.position === "string") {
document.write("<strong>Position:</strong> " + args.position + "<br />");
}
if (typeof args.average === "number") {
document.write("<strong>Average:</strong> " + args.average + "<br />");
}
if (typeof args.homeruns === "number") {
document.write("<strong>Home Runs:</strong> " + args.homeruns + "<br />");
}
if (typeof args.rbi === "number") {
document.write("<strong>Runs Batted In:</strong> " + args.rbi + "</p>");
}
}
showStatistics({
name: "Mark Teixeira"
});
showStatistics({
name: "Mark Teixeira",
team: "New York Yankees"
});
showStatistics({
name: "Mark Teixeira",
team: "New York Yankees",
position: "1st Base",
average: .284,
homeruns: 32,
rbi: 101
});
Technically, this second method of passing the arguments might
require a little bit more code, but with a large collection of
arguments, there are a few advantages.
First, the function itself is simplified because it accepts only one argument (args ), which is a collection of all the values passed from the object literal (name , team , position ,
etc). Plus, the actual argument values are easy to read, and can easily
be understood, updated, or modified, since the correlation between the
values and the argument references are more direct.
If the function required only a small number of arguments, then this
method would not be necessary, and might actually have the opposite
effect. So, use this technique sparingly, and only in situations where
you foresee the collection of arguments being hard to maintain over
time.
Further Reading
3. Contextual Targeting of DOM Elements
There are sometimes instances where you need to traverse the DOM and
gain access to a specific element, or group of elements, but due to
certain restrictions, you may not have direct access to the elements
via a CSS class name or ID in the HTML code. This might be because of
user-generated content produced through a rich text editor, or dynamic
content pulled from a database.
Whatever the case, it's not impossible to access those unidentified
DOM elements via JavaScript. Using what I call "contextual targeting",
you can gain access to, and modify, almost any element in the DOM. As
long as you have a map of the general template that contains the
element you want to target, you can access that element and manipulate
it the same way you would an element that has a class name or ID.
Let's create some basic HTML code that will serve as our example page:
<div id="header">
<h1>Site Title</h1>
</div>
<div id="sidebar">
<ul>
<li><a href="#">Testing</a></li>
<li><a href="#">Testing</a></li>
<li><a href="#">Testing</a></li>
<li><a href="#">Testing</a></li>
<li><a href="#">Testing</a></li>
<li><a href="#">Testing</a></li>
</ul>
</div>
<div id="content">
<h2>Page Title</h2>
<p><a href="#">Lorum Ipsum link here</a>. Pellentesque habitant morbi
tristique senectus et netus et malesuada fames ac turpis egestas.
Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet,
ante. Donec eu libero sit amet quam egestas semper.
Aenean ultricies mi vitae est. Mauris placerat eleifend leo.
Pellentesque habitant morbi tristique senectus et netus et malesuada
fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae,
ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam
egestas semper. Aenean ultricies mi vitae est. Mauris
placerat eleifend leo.</p>
<p><span style="color: red;">Pellentesque habitant morbi</span>
tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum
tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec
eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est.
Mauris placerat eleifend leo. Pellentesque habitant morbi tristique senectus
et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam,
feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit
amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat
eleifend leo.</p>
</div>
<div id="footer">
<p>Copyright | <a href="#">contact</a> | <a href="#">policy</a> |
<a href="#">privacy</a></p>
</div>
Using the HTML code above, if we wanted to target all the anchor
tags on the page, we could collect them and manipulate them like this:
var myLinkCollection = document.getElementsByTagName("a");
for (i=0;i<myLinkCollection.length;i++) {
// do something with the anchor tags here
}
If we wanted to target only the anchor tags in the footer, however,
we would target them based on their context, or surrounding elements,
like this:
var myFooterElement = document.getElementById("footer");
var myLinksInFooter = myFooterElement.getElementsByTagName("a");
for (i=0;i<myLinksInFooter.length;i++) {
// do something with footer anchor tags here
}
The first line grabs a reference to the footer element. The second line collects all <a>
tags inside the footer. Then we loop through them and do what we want
with them. Thus, they are accessible even though they are not grouped
via class names.
You can accomplish the same thing by using node properties, as shown below.
var myLinkCollection = document.getElementsByTagName("a");
for (i=0;i<myLinkCollection.length;i++) {
if (myLinkCollection[i].parentNode.parentNode.id === "footer") {
// do something with footer anchor tags here
}
}
Similar code could be used to target the lone anchor tag inside the "content" section.
We could also limit our anchor tag search to include only tags that have the href attribute set, so as to avoid finding any in-page links. We do this by using the getAttribute method:
var myLinkCollection = document.getElementsByTagName("a");
for (i=0;i<myLinkCollection.length;i++) {
if (myLinkCollection[i].getAttribute("href")) {
// do something with the anchor tags here
}
}
Finally, you'll notice that there is a <span> tag
with an inline style. The inline style could have been generated
through a content management system, so you may not have the ability to
edit it directly. You can target all <span> elements with inline styles like this:
var myLinkCollection = document.getElementsByTagName("span");
for (i=0;i<myLinkCollection.length;i++) {
if (myLinkCollection[i].getAttribute("style")) {
// do something with all anchors that have inline styles
}
}
The possibilities are endless with contextual targeting, and there
are even more options available if you're using a JavaScript library
that normalizes browser differences and simplifies DOM manipulation.
Further Reading:
4. Using Namespaces to Prevent Conflicts
If you're doing an extensive amount of raw JavaScript coding and
suspect that additions could be made to the same pages you're working
on, you can prevent any future conflicts with your code by giving your
code its own namespace.
Object-oriented JavaScript implements namespace-like principles due
to the fact that properties and methods are declared inside of objects,
thus there are less likely to be conflicts. A conflict could arise,
however, through object names. And very likely, the conflict will occur
"silently", thus you may not be alerted to the issue immediately.
You can prevent all conflicts by creating a unique namespace. Let's use the showStatistics function to demonstrate how we can encapsulate code into its own namespace:
if (typeof MY == "undefined") {
MY = new Object();
MY.CUSTOM = new Object();
}
MY.CUSTOM.namespace = function() {
function showStatistics(args) {
document.write("<p><strong>Name:</strong> " + args.name + "<br />");
document.write("<strong>Team:</strong> " + args.team + "<br />");
if (typeof args.position === "string") {
document.write("<strong>Position:</strong> " + args.position + "<br />");
}
if (typeof args.average === "number") {
document.write("<strong>Average:</strong> " + args.average + "<br />");
}
if (typeof args.homeruns === "number") {
document.write("<strong>Home Runs:</strong> " + args.homeruns + "<br />");
}
if (typeof args.rbi === "number") {
document.write("<strong>Runs Batted In:</strong> " + args.rbi + "</p>");
}
}
showStatistics({
name: "Mark Teixeira",
team: "New York Yankees",
position: "1st Base",
average: .284,
homeruns: 32,
rbi: 101
});
}
MY.CUSTOM.namespace();
The first few lines create the namespace by checking to see if the "MY "
object already exists. This object can be whatever you want it to be.
Just pick a name that you don't think will ever be used again. After
the MY object is created, we are then able to create the "CUSTOM " object as a property of the MY object. Then our namespace function becomes a method of the MY.CUSTOM object. Keep in mind that "MY ", "CUSTOM " and "namespace " can each be your own custom names. I chose these for demonstration purposes. They could be CHEESEBURGER.ONIONS.pickles if you want!
The showStatistics function is exactly the same as in
the example earlier that utilizes an object literal to pass in the
values. But in this case, the entire function, including the object
literal, is encapsulated inside my.custom.namespace . The
last line invokes the entire function using dot notation, and the
function runs exactly the same as it normally would, except that it is
protected from conflicting with another function called "showStatistics ".
Further Reading:
5. Hybrid Application Development
You can create powerful JavaScript applications if you use a
combination of a JavaScript library and raw JavaScript code. Many
JavaScript libraries are used to implement "pretty" animations and
other customizable effects-sometimes via plugins- that often don't
require much to be added to them other than some custom values.
On the other hand, there may be situations where you'll want to
accomplish something specificly requested by a client. Maybe it's
something not available in a library and that requires extensive
coding, possibly utilizing Ajax and a variety of DOM methods.
There is no point in reinventing the wheel. You can implement your
favorite JavaScript library and take advantage of its simplified Ajax
calls, DOM methods, and normalization of browser differences. Thus, you
can have the advantages of the library, while still creating custom
scripts that are specific to your project.
Further Reading:
6. Rendering Readable HTML
Finally, this is a technique to use in situations that require
dozens of lines of HTML code being generated dynamically via
JavaScript. Take the following example:
var pageContainer = document.getElementById("container");
var pageTitle = "Content Title";
var authorBio = "Mr. Lorum Ipsum";
var pageContent = "Lorum ipsum line text here. Lorum ipsum line text here.
Lorum ipsum line text here. Lorum ipsum line text here.
Lorum ipsum line text here. Lorum ipsum line text here.
Lorum ipsum line text here. Lorum ipsum line text here.
Lorum ipsum line text here.";
var footerContent = "Copyright 2009";
var HTMLCode = '\n<h1>' + pageTitle + '</h1>\n
<div id="content">\n
<p>' + pageContent + '</p>\n
<div id="author_bio">\n
<p>' + authorBio +'</p>\n
</div>\n
</div>\n
<div id="footer">
<p>' + footerContent + '</p>\n
</div>\n';
pageContainer.innerHTML = HTMLCode;
The line to take note of above is the one that declares the value of
the HTMLCode variable. It renders just find in the generated source
code, since it utilizes the "new line" character, so it looks like
perfectly good HTML. But if this line of code were any longer it would
be extremely difficult to read and maintain in the .js file.
Here is the same code as above, but implementing a much more organized method of displaying the HTML:
var pageContainer = document.getElementById("container");
var pageTitle = "Content Title";
var authorBio = "Mr. Lorum Ipsum";
var pageContent = "Lorum ipsum line text here. Lorum ipsum line text here.
Lorum ipsum line text here. Lorum ipsum line text here.
Lorum ipsum line text here. Lorum ipsum line text here.
Lorum ipsum line text here. Lorum ipsum line text here.
Lorum ipsum line text here.";
var HTMLCode = '\n' +
'<h1>' + pageTitle + '</h1>\n'
'<div id="content">\n' +
'<p>' + pageContent + '</p>\n' +
'<div id="author_bio">\n' +
'<p>' + authorBio + '</p>\n' +
'</div>\n' '</div>\n' +
'<div id="footer">' +
'<p>' + footerContent + '</p>\n' +
'</div>\n';
pageContainer.innerHTML = HTMLCode;
Now the code is much more readable, and conforms to the manner in
which HTML is rendered in an actual HTML page. It even includes proper
HTML indenting, and still uses the new line character to properly
format the outputted HTML.
Conclusion
Although I didn't provide a detailed explanation of every concept
dealt with in this collection, I hope this list provided beginning and
intermediate JavaScript coders with an overview of a few fairly
advanced practical techniques that they can implement in future
projects or experiments.
Please feel free to comment on any of the techniques I've mentioned
and some specific ways that you have used them in your own applications.
|