Series Synopsis
I see often JavaScript in the wild lacks a focus on maintainability. I want to cover some of the basics that have served me well in improving maintainability as I try to tame whatever project I am working on. These crop up with such a usual frequency I want to save my fellow programmers some reoccuring pain.
The topics I covered so far in this Maintainable JavaScript series:
- Part I: Fundamentals of an Object Oriented Approach (you are here)
Defining a Global Namespace
I am a C# developer for the majority of my work. Like my Objective C, C/C++, Java, and other object oriented brethren, I enjoy the ability to namespace my classes for everything on the servers. Unfortunately, JavaScript does not restrict nor does it encourage this good behavior. Namespaces absolutely isolate your code from other components' code. All major JavaScript libraries use this concept across the web. However it takes a little of finesse to integrate this technique into your code. One might ask, "Where is the namespace keyword?" JavaScript developers suffer from an overly clever language design. I find the more I learn about JavaScript the more I see the language designers' love affair with terse syntax.
Sample 1.1: Simple Namespace
var jrf = jrf || function(){};
console.log(typeof jrf);
var instance = new jrf();
console.log(typeof instance);
console.log(typeof jrf);
var instance = new jrf();
console.log(typeof instance);
Output to console:
function
object
The first line of Sample 1.1 uses the logical or operator in JavaScript. I am making an assumption that I want to only create the jrf object if it does not already exist. If I had the benefit of a namespace keyword in JavaScript, this is the exact same logic the interpreter would use to create the jrf object. The second line of Sample 1.1 spits out function to the console. All popular browser support the console DOM object. Although every browser seems to call the console window something different. Chrome, Firefox and Internet Explorer (IE) all support the keyboard shortcut of F12 for pulling up this window.
Many developers I meet do not know the new keyword exists in JavaScript. This example is a bit contrived as I am happy to use jrf as an object over a function for its destiny as a namespace. I will share more on why later. I do however think it is key from a code reuse standpoint to know that you can use functions as constructors to create JavaScript objects.
I highly recommend gaining familiarity with the developer tools of a few major browsers. All come with some level of basic functionality, the big players all come with pretty encompassing tools, and many also have developer plugins from third parties as well. All major browsers do a pretty good job of documenting their developer tools. Below I list a page for each of the major players that covers how to use the console for a particular browser. The immediate window functionality also inherit with many console tools can help test different concepts in quick order.
Popular Browsers and their Consoles
- Apple Safari: http://developer.apple.com/library/safari/#documentation/appleapplications/Conceptual/Safari_Developer_Guide/DebuggingYourWebsite/DebuggingYourWebsite.html
- Apple Safari on IOS: http://developer.apple.com/library/ios/#DOCUMENTATION/AppleApplications/Reference/SafariWebContent/DebuggingSafarioniPhoneContent/DebuggingSafarioniPhoneContent.html
- Google Chrome: https://developers.google.com/chrome-developer-tools/docs/console
- Microsoft Internet Explorer: http://msdn.microsoft.com/en-us/library/dd565625(v=vs.85).aspx#consolelogging
- Mozilla Firefox: https://developer.mozilla.org/en/Using_the_Web_Console
Nesting Namespaces
A common use of namespaces in other languages is concept of grouping code into different parts. Many people will call these parts modules or components. Terminology here is not as important as the concept. In JavaScript we nest functions to replicate the idea of namespace structures in other languages. Unlike statically type object oriented languages a programmer has very few restrictions in a dynamically typed language like JavaScript. Again JavaScript does not force a programmer to pick one coding style over others. Using the namespace pattern within Javascripte code helps avoid a general trouble maker and also a supposed feature, Hoisting.
Sample 1.2: Hoisting
hoistedFunction();
console.log(count);
function hoistedFunction(){
count = 12;
console.log("Where am I?");
}
console.log(count);
function hoistedFunction(){
count = 12;
console.log("Where am I?");
}
Output to console:
Where am I?
12
Sample 1.2 is a cautionary tale and frequent source of endless hours debugging JavaScript. The count variable and hoistedFunction end up in the global scope. The JavaScript parser searches for all globally defined code first before running the rest of the code. I could even define count multiple times in the same fashion. As you might expect this will lead to rather unpredictable results throughout my code at first glance.
Sample 1.3: Nesting
var jrf = jrf || {};
jrf.grid = jrf.grid || {};
console.log(jrf.grid);
var grid = new jrf.grid(); //error
jrf.grid = jrf.grid || {};
console.log(jrf.grid);
var grid = new jrf.grid(); //error
Output to console:
Object
Uncaught TypeError: object is not a function
In Sample 1, I made jrf a function to show you how to create a object using the new keyword. I suggest leaving off the function () as in Sample 1.3. This makes jrf an object. The error I get off of trying to use the new keyword when assigning the grid variable is a desired effect. This also has the terse side effect of less required typing. The key here is making jrf.grid an object. This gives it the semantic meaning meshes well with the concept of a namespace.
Defining Types
Commonly web sites page content through some mechanism for performance reasons. I will use this as our domain for defining types and throughout the rest of this series. For now I will concentrate on creating a gridMetadata function off of the jrf.grid object. gridMetadata could be used as view model for a bit of HTML I will create later on in this series called a pager.
Sample 1.4: Best Practice: Type Creation using object Prototype
var jrf = jrf || {};
jrf.grid = jrf.grid || {};
jrf.grid.gridMetadata = jrf.grid .gridMetadata || function (){};
jrf.grid.gridMetadata.prototype.count = 25;
jrf.grid.gridMetadata.prototype.currentPage = 0;
var data = new jrf.grid.gridMetadata();
data.currentPage = 2;
console.log(data.count);
console.log(data.currentPage);
jrf.grid = jrf.grid || {};
jrf.grid.gridMetadata = jrf.grid .gridMetadata || function (){};
jrf.grid.gridMetadata.prototype.count = 25;
jrf.grid.gridMetadata.prototype.currentPage = 0;
var data = new jrf.grid.gridMetadata();
data.currentPage = 2;
console.log(data.count);
console.log(data.currentPage);
Output to console:
25
2
In Sample 1.4 I defined count and currentPage on jrf.grid.gridMetadata using the prototype attribute that the DOM exposes on all Javascript functions. I intentionally defined them this way, as it allows you to define jrf.grid.gridMetadata accross several JavaScript files. Notice count keeps the default value of 25 even in the data object. This is a handy way to setup default values and even objects for members.
Sample 1.5: We lost one
var jrf = jrf || {};
jrf.grid = jrf.grid || {};
jrf.grid.gridMetadata = jrf.grid.gridMetadata || function (){};
jrf.grid.gridMetadata.prototype = {
count: 25,
currentPage: 0
};
var data = new jrf.grid.gridMetadata();
data.currentPage = 2;
console.log(data.count);
console.log(data.currentPage);
//Imagine following lines are in a seperate JavaScript file
jrf.grid.gridMetadata = jrf.grid.gridMetadata || function (){
lostMember: 51
};
var moreData = new jrf.grid.gridMetadata();
console.log(moreData.lostMember); //undefined
jrf.grid = jrf.grid || {};
jrf.grid.gridMetadata = jrf.grid.gridMetadata || function (){};
jrf.grid.gridMetadata.prototype = {
count: 25,
currentPage: 0
};
var data = new jrf.grid.gridMetadata();
data.currentPage = 2;
console.log(data.count);
console.log(data.currentPage);
//Imagine following lines are in a seperate JavaScript file
jrf.grid.gridMetadata = jrf.grid.gridMetadata || function (){
lostMember: 51
};
var moreData = new jrf.grid.gridMetadata();
console.log(moreData.lostMember); //undefined
Output to console:
25
2
undefined
The most common problem with order of scripts is dependencies are missing due to ordering of scripts. Sample 1.5 illustrates another more subtle problem. If I define members for a given function accross JavaScript files I will lose all those not defined with the first declaration. This is not the only reason to use object prototypes. For more on why prototypes are good I suggest checking out this blog entry.
Summary
JavaScript is a terse language by design like many of its dynamic brethren. It can be tricky at first for developers from other paradigms to master this style of programming. I hope with this entry you feel more comfortable with the subtle turns within JavaScript.