[Coding] JavaScript Patterns (Ch. 5)
Posted by Khatharsis on June 19, 2013
This chapter dealt more in-depth with objects and patterns to use objects for namespacing, dependency declaration, modules, and sandboxing. Objects are also used for private, privileged, static, private static members, constants, chaining, and a class-inspired way to define a constructor.
Namespacing isn’t built into JavaScript as it is in classical languages. Namespacing is nice because it helps reduce naming collisions or name prefixing, not to mention, reducing the number of global variables. Convention is to use all caps, but the same convention is also used for constants. This pattern is also mentioned in Crockford’s book and is one I have been trying to make use of when I start new projects:
var APP = {};
APP.Parent = function() { ... };
APP.aVar = 1;
APP.anObj = {};
APP.anObj.aProperty = true;
The drawbacks of this pattern include more to type with the namespacing prefix, one global instance that is easily modified by code anywhere else, and nested names result in longer property lookups. These drawbacks are addressed in another pattern (sandbox).
Since code can be easily modified by code elsewhere, it is a good idea to check that the namespace exists before accidentally overwriting it:
var APP = APP || {};
And a similar check for any namespaces that get added to the parent namespace (from the book):
MYAPP.namespace = function (ns_string) {
var parts = ns_string.split('.'),
parent = MYAPP,
i;
// strip redundant leading global
if (parts[0] === "MYAPP") {
parts = parts.slice(1);
}
for (i = 0; i < parts.length; i += 1) {
// create a property if it doesn't exist
if (typeof parent[parts[i]] === "undefined") {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
};
This particular function ensures that if a namespace exists, it will not overwrite that namespace. Declaring a namespace is simply:
var module = MYAPP.namespace(MYAPP.module);
--
Often used with libraries, dependencies are as simple as pointing to the library modules that your code requires. A good practice is to declare the dependencies at the top of the code with a local variable:
var event = YAHOO.util.event;
It is easy to see which modules (actual files) need to be included with the page and to find and resolve dependencies. Creating a local variable means less typing is necessary, resulting in less overall code, and is faster for resolution.
--
All object members are public in JavaScript, but private members can be created using closure. This concept was touched on in Chapter 4. Public methods in a constructor are methods that are returned. These methods have access to the private variables.
function Thing() {
var thing1= 'A';
this.getThing = function() {
return thing1;
}
}
var thing = new Thing();
thing.name; // undefined
thing.getThing(); // 'A'
In the above example, getThing() is a privileged method, meaning it is a public method that has access to a private variable.
One thing to keep in mind is to consider the case if thing1 were an array or object, rather than a primitive. getThing() returns thing1, which is now an array or object. thing1 can now be modified because a reference to thing1 is returned, rather than a value or copy of it. Aside from simply avoiding returning objects and arrays or the desired properties/values from them, additional code can be written to return a copy of the array or object. This code is provided in a future chapter.
Privacy can also be achieved using literals, rather than constructors. The concept is the same as before, using closure to create private members, but using an anonymous immediate function to create that closure:
var anObj;
(function() {
var privateVar = 'foo';
anObj = {
getVar: function() {
return privateVar;
}
};
})();
// OR similarly...
var anObj = (function() {
var privateVar = 'foo';
return {
getVar: function() {
return privateVar;
}
};
})();
--
To avoid duplication and save memory when creating common properties and methods with constructors, make use of the prototype property of the constructor. Hidden private members can be shared among instances using a combination of private properties inside constructors and private properties in object literals:
Thing.prototype = (function() {
var sharedThing = 'B';
return {
getSharedThing: function() {
return sharedThing;
}
};
})();
var thing = new Thing();
thing.getThing(); // 'A'
thing.getSharedThing(); // 'B'
--
The revelation pattern is the concept of exposing private functions for public use but protecting those functions from tampering at the same time. This sounds confusing and from the example provided in the book, I understood it as aliasing functions. Similar to how PHP has count and sizeof to determine the length of an array, the idea in the revelation pattern is if one of the functions (e.g., sizeof) gets accidentally modified, the other function (count) remains as it was originally defined. The pattern looks something like:
var arr;
(function() {
function count(a) {
return a.length;
}
arr = {
count: count,
sizeof: count
};
})();
--
The module pattern provides the tools to create self-contained code which can be added, replaced, or removed. This pattern combines the namespaces, immediate functions, private and privileged members, and declaring dependencies patterns. The basic template looks like:
MYAPP.namespace('MYAPP.utilities.thing');
MYAPP.utilities.thing = (function() {
// Declare dependencies
var other = MYAPP.utilities.other,
// Declare private properties, methods
myThing = 'C',
organizeThing = function() { ... };
// Any one-time init tasks
// ...
// Public API
return {
adjustThing: function(t) { ... },
removeThing: function(t) { ... }
};
})();
A variation of this basic template is the revealing module pattern and is similar to the revelation pattern. Rather than return functions that are defined in the return object block as in the above example, return the names/aliases of the private functions which get exposed for public use. Another variation is instead of returning an object, return a function so the invocation can be used with new. A final variation that is mentioned is to pass the global variable and the global object itself. This variation helps to speed up resolution inside of the immediate function because the imported variables become local variables for the function.
--
Finally, the sandbox pattern is discussed. Recall the issues with namespacing involve unique variables and long dotted names. The sandbox pattern alleviates these issues by providing an environment for modules without affecting other modules.
The sandbox pattern relies on the single global as a constructor. Objects are created with it. Sandboxes are created by passing in a callback function (from the book):
new Sandbox(function (box) {
// your code here...
});
A variation of this basic form is to accept parameters before the callback function which are names of any modules this sandbox relies on, similar in form to apply() or call(). Another variation is to always assume a new Sandbox will be created so the new keyword is not necessary.
The Sandbox object is an object, so it can have a static property that contains things like modules:
Sandbox.modules = {};
Sandbox.modules.module1 = function(box) {
box.doSomething = function() { ... };
box.doSomethingElse = function() { ... };
};
I'll leave out the actual code for the constructor function of Sandbox, but it combines patterns previously discussed like making sure this is an instance of Sandbox and if not, call the function again as a constructor (covered in Chapter 3, constructor functions). The callback is invoked at the end using the newly created instance. The callback itself is the sandbox with the box object containing all of the requested functionality.
--
As with the lack of a private keyword, JavaScript lacks a static keyword. However, this functionality can be emulated. Using a constructor function and a property, public static members look and act similar to static members of classical languages:
Sandbox.modules();
The interesting difference takes place when adding a property directly to the object vs. the object's prototype:
var Thing = function() {};
Thing.greet = function() {
return "Hello.";
};
Thing.prototype.setType = function(type) {
this.type = type;
};
Thing.greet(); // "Hello."
var myThing = new Thing();
myThing.setType("Round");
typeof Thing.setType; // undefined
typeof myThing.greet; // undefined
Where setType() is an instance method and requires myThing and greet() is a static method and does not require myThing. Conversely, myThing cannot access greet() nor can Thing access setType(). If you want the static method to be available to instances, just add the following:
Thing.prototype.greet = Thing.greet;
Take caution when making static methods available to instances as this will point to different objects. In the static version, this will point to Thing, but in the instance version, this will point to myThing. A variation to take advantage of this difference is to use instanceof to determine if it is statically or nonstatically invoked, then have different behavior for each method of invocation.
The above example discussed public static members. Next, the book discusses private static members, which are members that are shared by all objects created with the same constructor function and not accessible outside of the constructor. The premise builds on the concept of closure discussed in the private variables section earlier in the chapter. What occurs is the wrapper function which creates the closure for the private variable is invoked immediately, returning a new function:
var Counter = (function() {
var id = 0;
return function() {
console.log(id++);
};
})();
var aCounter = new Counter(); // 1
var anotherCounter = new Counter(); // 2
--
JavaScript does not have any constants. Constants can be emulated by using properties in all caps, but these values can be changed simply by setting it to another value. To create an immutable value, a private property can be created with a public getter method.
--
The chaining pattern enables methods to be called one after the other simply by returning this. Chaining is often used in jQuery to perform multiple functions on a single object, like a node in the DOM tree. Methods that have no meaningful return value can return this instead, allowing for chaining to be used. A couple of pros of the chaining pattern is it makes it quicker to read code as it may sometimes read like a sentence and it encourages smaller, more specialized functions to be written which improves maintainability. A drawback is it becomes difficult to debug which method in the chain is behaving oddly (another name for this pattern is "train wreck").
--
The method() method introduced by Crockford is an attempt to make JavaScript more class-like, even though it is not a good idea to do so. prototype is a new concept for coders who come from classical backgrounds. The method() pattern hides prototype away, letting the classical coders write their code as they are used to. method() looks like the following (from the book):
if (typeof Function.prototype.method !== "function") {
Function.prototype.method = function (name, implementation) {
this.prototype[name] = implementation;
return this;
};
}
method() takes two parameters, the name of the new method and its implementation:
var Thing = function(type) {
this.type = type;
}.
method('getType', function() {
return this.type;
});
method() is chained onto the creation of the Thing "class" and can continue to be chained. Then, to make an instance of Thing and use its methods, it will look like:
var myThing = new Thing('ball');
myThing.getType(); // 'ball'
--
Again, a lot of material in this chapter with interesting ways of using objects to achieve certain effects. JavaScript is like a potpourri of different types of languages and with many programmers approaching it from a classical background, the language can seem illogical. I think that is what draws me to it, to continually pursue mastery over the language, is that there are many neat things it is capable of that classical languages feel too restricted or bounded. Just as some things can be done more easily and concisely in procedural languages than classical languages, I feel JavaScript has an area that it excels in with its nuances.
I'd also like to throw in a note here about minification which the author has brought up a few times, but I have left out of my reviews. Some of the patterns are not minification-friendly because minifiers find them unsafe to minify for some reason or another. I'm not yet ready to think about minifiers, but they should be considered when looking at the pros and cons of some of these patterns, especially in optimization-heavy environments.