Loading...
Loading...
Marco Franssen

Most of you have seen plenty of spaghetti JavaScript. If you're reading this, you probably don't want to make the same mistakes. Let's take the next step: write modular JavaScript and stop polluting the global namespace.
Why is it a bad idea to keep all your script code in the global namespace?
First, you risk name collisions when you use other people's modules. Their variables might have the same names as yours.
Second, global names can limit how effectively your code can be minified.
Third, you're not pushing yourself to organize your JavaScript into clean modules.
If you think I'm talking bullshit, you might as well stop reading and carry on scaring other developers away with spaghetti code.
First, we need to learn how to write a JavaScript module. There are plenty of patterns to choose from. This article explains the module pattern in depth. Another well-known option is the object literal pattern.
For my example, I'll use a pattern like the one in the first article, because it lets me control which functions are visible outside the module.
var shoppingCart = (function () {
var items = [];
var priceTotal = 0;
var addProduct = function (product) {
items.push(product);
updatePriceTotal();
};
var removeProduct = function (product) {
//remove product from items
updatePriceTotal();
};
var updatePriceTotal = function () {
//logic to update the priceTotal
//use public functions on product to get the price of products
};
return {
addProduct: addProduct,
removeProduct: removeProduct,
};
})();var product = (function () {
//an implementation like shoppingCart
})();This is a deliberately bare example, just enough to make my point. Each JavaScript module lives in a separate file, which makes maintenance easier. In shoppingCart, you can see how to control visibility: the updatePriceTotal function is private to the module.
This looks a lot like organizing code in C# or Java, doesn't it? Apart from the syntax, of course.
But we're still polluting the global namespace. There are only two global variables now, but imagine finishing this application. How many more modules would we add? How many more global names?
You can see this in Chrome's developer tools. Press F12, open the console, and type product or shoppingCart. Press Enter, and you'll see that both objects are accessible from the global namespace.
So how can we wrap these modules in our own namespace?
(function (jsShop, window, document, undefined) {
var product = jsShop.product;
var shoppingCart = (jsShop.shoppingCart =
jsShop.shoppingCart ||
(function () {
var items = [];
var priceTotal = 0;
var addProduct = function (product) {
items.push(product);
updatePriceTotal();
};
var removeProduct = function (product) {
//remove product from items
updatePriceTotal();
};
var updatePriceTotal = function () {
//logic to update the priceTotal
//use public functions on product to get the price of products
};
return {
addProduct: addProduct,
removeProduct: removeProduct,
};
})());
})((window._jsShop = window._jsShop || {}), window, document);(function (jsShop, window, document, undefined) {
var product = (jsShop.product =
jsShop.product ||
(function () {
//an implementation like shoppingCart
})());
})((window._jsShop = window._jsShop || {}), window, document);We've added a small wrapper around our modules. It's a self-executing function that receives the globals we need. When we call it, we pass in our own namespace, registering that namespace globally if it doesn't already exist. We also pass window and document, with a final parameter named undefined. Local parameters can help with minification and avoid repeated global lookups. You can leave unused parameters out, but I tend to include them so I don't forget them later. Notice that we don't pass an argument for the last parameter, so its value is undefined.
Another useful practice is to start each script with a semicolon. That helps prevent errors when files are concatenated and the preceding script is missing a trailing semicolon, a problem you may encounter when bundling and minifying scripts.
Finally, we register the module in our namespace. We check whether it already exists and use the existing module if it does; otherwise, we assign the new module definition.
What if you want to use jQuery or another library? Just pass it into the wrapper so you can use it inside your module.
(function ($, jsShop, window, document, undefined) {
var yourModule = (jsShop.yourModule =
jsShop.yourModule ||
(function () {
//your module code
$("#shoppingCatTotalPrice").html("€ 8,75");
})());
})(jQuery, (window._jsShop = window._jsShop || {}), window, document);Now product and shoppingCart no longer occupy their own names in the global namespace. Only our shared namespace is global. I've prefixed it with an _ to reduce the chance of a collision with another script. Try it in Chrome's developer tools: press F12, type _jsShop in the console, and press Enter. You should see both modules registered there. Google Analytics also used underscore-prefixed globals for its API.
For more on JavaScript namespacing, read this article. My example uses one of Addy Osmani's preferred patterns.
Organizing JavaScript into modules also sets us up to explore asynchronous dependency loading with RequireJS. For the next step, see Addy Osmani's article on AMD (Asynchronous Module Definition). I plan to pick up from here in a separate step-by-step article.
Also have a look at my article on using jQuery events to keep JavaScript clean for a deeper dive into modules that communicate through events. I didn't use proper namespacing there, but now you know how to add it.
I hope you enjoyed this article. Let's build better JavaScript together! Share it with your friends and colleagues, and give me some feedback so I can learn from you too.
One last tip…
Subscribe to Addy Osmani's RSS feed.
Marco Franssen
Install Windows 8 Consumer Preview on a virtual hard disk alongside Windows 7, from creating a bootable USB drive to configuring the first boot.
Marco Franssen
Write a personal elevator pitch that highlights your strengths, builds credibility, and invites a conversation with practical tips on persuasion.
Marco Franssen
See how I wrapped the Ncqrs command service to retry commands after concurrency exceptions, with results from a simple asynchronous JavaScript test.
Marco Franssen
Install Windows 8 Developer Preview on a virtual hard disk and boot it alongside Windows 7, using a bootable USB drive and diskpart.