JavaScript design patterns are important for the maintainability and scalability of web applications. While working on the AddThis Smart Layers product, the team focused on writing DRY (Don’t Repeat Yourself), consistent, and cross-browser compliant code. Before we talk about the specific techniques that we used, let’s first understand the Smart Layers use case.
Use Case
Smart Layers is a product suite that currently includes 6 different UI widgets (including share, follow, recommended content). Although each widget contains unique functionality, there are many similarities between the widgets.
For example, all widgets create and append DOM elements, listen to events, support user-specified options, utilize CSS3 show/hide animations, etc. Once we put our code architect hats on, we realized that:
We needed an inheritance model that could handle both unique widget functionality and common logic without code duplication.
Simple JavaScript Prototypal Inheritance
At this point, it is important to delve into object-oriented JavaScript, and recognize the difference between instance properties and prototype properties.
Instance Properties
Instance properties are properties that are scoped to a particular instance object and are not propagated to other instances (unless you go out of your way to change that). Let’s look at an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
In the previous example, each instance object contains one instance property (name). Since each instance object’s properties are not propagated to each other, they are not aware of each other’s changes.
Prototype Properties
On the other hand, prototype properties are properties that are propagated to all instances. In JavaScript, an object’s property is looked up at run time, by first checking if there is an instance property. If there is not an instance property, then the object’s prototype is checked next to see if it contains the property.
All prototype properties are kept in sync and visible to each instance.
Let’s demonstrate prototypes by expanding upon our previous code example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
For Smart Layers, every widget instance reuses the same object as it’s prototype. This both simplifies the codebase and makes it easy to make changes that need to be propagated to all widgets.
Factory Pattern
Since every widget is created in a similar way, we needed a design pattern to take care of the instance creation process. Smart Layers uses the Factory pattern as a generic interface for creating instance objects.
Here’s a high-level look:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
The previous example shows a factory()
method that can be called by passing a string name and an object literal containing all of the desired instance properties.
The factory pattern abstracts away the JavaScript constructor and prototype assigning ugliness, and provides an easy way to implement a consistent API for each instance.
Note: For similar techniques that work with jQuery plugins, check out the jQueryUI Widget Factory and jqfactory open-source projects.
Module Pattern
Another major design pattern used in Smart Layers is the module pattern. Currently, the Smart Layers JavaScript API only has one public method (destroy) that can be used after Smart Layers is initialized on the page. Under the hood though, the JavaScript API consists of multiple hidden methods that are used internally. To accomplish public/private API methods, we took advantage of JavaScript function scoping to only expose what we wanted to.
Let’s look at a basic example of the module pattern:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
This example demonstrates that all local variables, declared using the var
keyword, are only accessible within the declaring function or other nested functions.
In our example, a person’s first name, last name, and occupation are all local variables. An object, that includes the firstName
property, is then returned inside of our constructor function. Since the firstName
local variable is the only variable returned, it is the only property that can be retrieved from a different scope.
The module pattern is an example of a closure, since a local property is kept alive and can be referenced inside of a different scope.
Here’s a more practical example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
This demonstrates the technique that Smart Layers uses to expose public API methods. All methods that start with an underscore are assumed to be private and are not returned as a public method.
Summary
Prototypal inheritance, the factory pattern, and the module pattern are all extremely useful techniques that can be used together to create powerful applications. I’d love to hear your thoughts and the patterns you use in your own JavaScript codebases!