JavaScript development has changed markedly from how it looked when it was first conceived. It’s easy to forget how far the language has come from its initial implementation in Netscape’s browser, to the powerful engines of today, such as Google’s V8. It’s been a rocky path involving renaming, merging, and the eventual standardization as ECMAScript. The capabilities we have today are beyond the wildest dreams of those early innovators.
Despite its success and popularity, JavaScript is still widely misunderstood. Few people know that it’s a powerful and dynamic object-oriented language. They’re surprised to learn about some of its more advanced features, such as prototypal inheritance, modules, and namespaces. So, why is JavaScript so misunderstood?
Part of the reason is due to previous buggy JavaScript implementations, and part of it is due to the name—the Java prefix suggests it’s somehow related to Java; in reality, it’s a totally different language. However, I think the real reason is the way most developers are introduced to the language. With other languages, such as Python and Ruby, developers usually make a concerted effort to learn the language with the help of books, screencasts, and tutorials. Until recently, though, JavaScript wasn’t given that endorsement. Developers would get requests to add a bit of form validation—maybe a lightbox or a photo gallery—to existing code, often on a tight schedule. They’d use scripts they’d find on the Internet, calling it a day with little understanding of the language behind it. After that basic exposure, some of them might even add JavaScript to their resumes.
Recently, JavaScript engines and browsers have become so powerful that building full-blown applications in JavaScript is not only feasible, but increasingly popular. Applications like Gmail and Google Maps have paved the way to a completely different way of thinking about web applications, and users are clamoring for more. Companies are hiring full-time JavaScript developers. No longer is JavaScript a sublanguage relegated to simple scripts and a bit of form validation—it is now a standalone language in its own right, realizing its full potential.
This influx of popularity means that a lot of new JavaScript applications are being built. Unfortunately, and perhaps due to the language’s history, many of them are constructed very poorly. For some reason, when it comes to JavaScript, acknowledged patterns and best practices fly out the window. Developers ignore architectural models like the Model View Controller (MVC) pattern, instead blending their applications into a messy soup of HTML and JavaScript.
This book won’t teach you much about JavaScript as a language—other books are better suited for that, such as Douglas Crockford’s JavaScript: The Good Parts (O’Reilly). However, this book will show you how to structure and build complex JavaScript applications, allowing you to create incredible web experiences.
The secret to making large JavaScript applications is to not make large JavaScript applications. Instead, you should decouple your application into a series of fairly independent components. The mistake developers often make is creating applications with a lot of interdependency, with huge linear JavaScript files generating a slew of HTML tags. These sorts of applications are difficult to maintain and extend, so they should be avoided at all costs.
Paying a bit of attention to an application’s structure when you start building it can make a big difference to the end result. Ignore any preconceived notions you have about JavaScript and treat it like the object-oriented language that it is. Use classes, inheritance, objects, and patterns the same way you would if you were building an application in another language, such as Python or Ruby. Architecture is critical to server-side applications, so why shouldn’t the same apply to client-side apps?
The approach this book advocates is the MVC pattern, a tried and tested way of architecting applications that ensures they can be effectively maintained and extended. It’s also a pattern that applies particularly well to JavaScript applications.
MVC is a design pattern that breaks an application into three parts: the data (Model), the presentation layer (View), and the user interaction layer (Controller). In other words, the event flow goes like this:
The user interacts with the application.
The controller’s event handlers trigger.
The controller requests data from the model, giving it to the view.
The view presents the data to the user.
Or, to give a real example, Figure 1-1 shows how sending a new chat message would work with Holla.
The user submits a new chat message.
The controller’s event handlers trigger.
The controller creates a new Chat Model record.
The controller then updates the view.
The user sees his new chat message in chat log.
The MVC architectural pattern can even be implemented without libraries or frameworks. The key is to divide up the responsibilities of the MVC components into clearly defined sections of code, keeping them decoupled. This allows for independent development, testing, and maintenance of each component.
Let’s explore the components of MVC in detail.
The model is where all the application’s data objects are stored. For example, we might have a User Model that contains a list of users, their attributes, and any logic associated specifically with that model.
A model doesn’t know anything about views or controllers. The only thing a model should contain is data and the logic associated directly with that data. Any event handling code, view templates, or logic not specific to that model should be kept well clear of it. You know an application’s MVC architecture is violated when you start seeing view code in the models. Models should be completely decoupled from the rest of your application.
When controllers fetch data from servers or create new records, they wrap them in model instances. This means that our data is object oriented, and any functions or logic defined on the model can be called directly on the data.
So, rather than this:
var user = users["foo"]; destroyUser(user);
We can do something like this:
var user = User.find("foo"); user.destroy();
The first example is not namespaced or object oriented. If we have
another destroyUser()
function
defined in our application, the two will conflict. Global variables and
functions should always be kept to an absolute minimum. In the
second example, the destroy()
function is namespaced behind User instances, as are all the stored
records. This is ideal, since we’re keeping global variables to a
minimum, exposing fewer areas to potential conflicts. The code is
cleaner and can take advantage of inheritance so functions like destroy()
don’t have be defined separately on
every model.
Models are explored in much more depth in Chapter 3, which covers topics such as loading in data from servers and creating object-relational mappers (ORMs).
The view layer is what’s presented to the user and is what she interacts with. In a JavaScript application, the view would be made up mostly of HTML, CSS, and JavaScript templates. Apart from simple conditional statements in templates, the views shouldn’t contain any logic.
In fact, like models, views should also be decoupled from the rest of the application. Views shouldn’t know anything about controllers and models—they should be independent. Mixing up views with logic is one of the surest paths to disaster.
That isn’t to say MVC doesn’t allow for presentational logic—as long as it’s not defined inside views. Presentational logic resides in what are called helpers: scripts solely for small utility functions related to the view.
The example below, which includes logic inside views, is something you shouldn’t do:
// template.html <div> <script> function formatDate(date) { /* ... */ }; </script> ${ formatDate(this.date) } </div>
In the code above, we’re inserting the formatDate()
function directly into the view,
which violates MVC, resulting in an unmaintainable mess of tag soup. By
separating out presentational logic into helpers, as with the example
below, we’re avoiding that problem and keeping our application’s
structure MVC-compliant.
// helper.js var helper = {}; helper.formatDate = function(){ /* ... */ }; // template.html <div> ${ helper.formatDate(this.date) } </div>
In addition, all presentational logic is namespaced under the
helper
variable, preventing conflicts
and keeping the code clean and extendable.
Don’t worry too much about specifics regarding views and templates—we cover them extensively in Chapter 5. The aim of this section is to familiarize you with how views relate to the MVC architectural pattern.
Controllers are the glue between models and views. Controllers receive events and input from views, process them (perhaps involving models), and update the views accordingly. The controller will add event listeners to views when the page loads, such as those detecting when forms are submitted or buttons are clicked. Then, when the user interacts with your application, the events trigger actions inside the controllers.
You don’t need any special libraries or frameworks to implement controllers; here’s an example using plain old jQuery:
var Controller = {}; // Use a anonymous function to enscapulate scope (Controller.users = function($){ var nameClick = function(){ /* ... */ }; // Attach event listeners on page load $(function(){ $("#view .name").click(nameClick); }); })(jQuery);
We’re creating a users
Controller that is namespaced under the Controller
variable. Then, we’re using an
anonymous function to encapsulate scope, preventing variable pollution
of the global scope. When the page loads, we’re adding a
click event listener to a view element.
As you can see, controllers don’t require a library or framework. However, to comply with MVC’s architectural requirements, they must be separated from Models and Views. Controllers and states are covered in more detail in Chapter 4.
Before we get to the nitty-gritty of MVC, we’re going to cover some preliminary concepts, such as JavaScript classes and events. This will give you a solid foundation before moving on to some of the more advanced concepts.
JavaScript object literals are fine for static classes, but it’s often useful to create classical classes with inheritance and instances. It’s important to emphasize that JavaScript is a prototype language, and as such doesn’t include a native class implementation. However, support can be emulated fairly easily.
Classes in JavaScript often get a bad rap, criticized for not being part of the “JavaScript Way,” a term that means essentially nothing. jQuery is effectively neutral when it comes to structural methodology or inheritance patterns. This can lead JavaScript developers to believe they shouldn’t consider structure—i.e., that classes aren’t available or shouldn’t be used. In reality, classes are just another tool, and as a pragmatist, I believe they’re as useful in JavaScript as in any other modern language.
Rather than class definitions, JavaScript has constructor functions
and the new
operator. A constructor function can specify an object’s initial
properties and values when it is instantiated. Any JavaScript function can
be used as a constructor. Use the new
operator with a constructor function to create a new instance.
The new
operator changes a
function’s context, as well as the behavior of the return
statement. In practice, using new
and constructors is fairly similar to languages with native class
implementations:
var Person = function(name) { this.name = name; }; // Instantiate Person var alice = new Person('alice'); // Check instance assert( alice instanceof Person );
By convention, constructor functions are upper camel-cased to
differentiate them from normal functions. This is important because you
don’t ever want to call a constructor function without the new
prefix.
// Don't do this! Person('bob'); //=> undefined
The function will just return undefined, and since the context is
the window (global) object, you’ve unintentionally created a global
variable, name
. Always call constructor
functions using the new
keyword.
When a constructor function is called with the new
keyword, the context switches from global (window) to a new and
empty context specific to that instance. So, the this
keyword refers to the current instance.
Although it might sound complicated, in practice, you can treat it like
native class implementations in other languages.
By default, if you don’t return anything from a constructor function,
this
—the current context—will be
returned. Otherwise, you can return any nonprimitive type. For example, we
could return a function that would set up a new class, the first step in
building our own class emulation library:
var Class = function(){ var klass = function(){ this.init.apply(this, arguments); }; klass.prototype.init = function(){}; return klass; }; var Person = new Class; Person.prototype.init = function(){ // Called on Person instantiation }; // Usage: var person = new Person;
Confusingly, due to a JavaScript
2 specification that was never implemented, class
is a reserved keyword. The common convention is instead to
name class
variables as _class
or klass
.
Adding class functions to a constructor function is the same as adding a property onto any object in JavaScript:
Person.find = function(id){ /*...*/ }; var person = Person.find(1);
To add instance functions to a constructor function, you need to use
the constructor’s prototype
:
Person.prototype.breath = function(){ /*...*/ }; var person = new Person; person.breath();
A common pattern is to alias a class’ prototype
to fn
, which is a bit less verbose:
Person.fn = Person.prototype; Person.fn.run = function(){ /*...*/ };
In fact, you’ll see this pattern throughout jQuery plug-ins, which
essentially just add functions to jQuery’s prototype, aliased to jQuery.fn
.
Currently, our class library includes functionality for instantiating and initializing instances. Adding properties to classes is the same as adding properties to constructor functions.
Properties set directly on the class will be equivalent to static members:
var Person = new Class; // Static functions are added directly on the class Person.find = function(id){ /* ... */ }; // And now we can call them directly var person = Person.find(1);
And properties set on the class’ prototype are also available on instances:
var Person = new Class; // Instance functions are on the prototype Person.prototype.save = function(){ /* ... */ }; // And now we can call them on instances var person = new Person; person.save();
However, in my opinion, that syntax is a little convoluted,
impractical, and repetitive. It’s difficult to see, at a glance, a list of
your class’ static and instance properties. Instead, let’s create a
different way of adding properties to our classes using two functions,
extend()
and include()
:
var Class = function(){ var klass = function(){ this.init.apply(this, arguments); }; klass.prototype.init = function(){}; // Shortcut to access prototype klass.fn = klass.prototype; // Shortcut to access class klass.fn.parent = klass; // Adding class properties klass.extend = function(obj){ var extended = obj.extended; for(var i in obj){ klass[i] = obj[i]; } if (extended) extended(klass) }; // Adding instance properties klass.include = function(obj){ var included = obj.included; for(var i in obj){ klass.fn[i] = obj[i]; } if (included) included(klass) }; return klass; };
In the improved class library above, we’re adding an extend()
function to generated classes, which
accepts an object. The object’s properties are iterated through and copied
directly onto the class:
var Person = new Class; Person.extend({ find: function(id) { /* ... */ }, exists: functions(id) { /* ... */ } }); var person = Person.find(1);
The include()
function works in
exactly the same way, except properties are copied onto the class’
prototype, rather than directly onto the class. In other words, the
properties are on the class’ instance, rather than statically on the
class.
var Person = new Class; Person.include({ save: function(id) { /* ... */ }, destroy: functions(id) { /* ... */ } }); var person = new Person; person.save();
We’re also implementing support for extended
and included
callbacks. If these properties are present on the passed object,
they’ll be invoked:
Person.extend({ extended: function(klass) { console.log(klass, " was extended!"); } });
If you’ve used classes in Ruby, this should all look very familiar. The beauty of this approach is that we’ve now got support for modules. Modules are reusable pieces of code, and they can be used as an alternative to inheritance for sharing common properties among classes.
var ORMModule = { save: function(){ // Shared function } }; var Person = new Class; var Asset = new Class; Person.include(ORMModule); Asset.include(ORMModule);
We’ve been using the prototype
property a lot, but it hasn’t really
been explained yet. Let’s take a closer look at what it is exactly and how
to use it to implement a form of inheritance in our classes.
JavaScript is a prototype-based language and—rather than make distinctions between classes and instances—it has the notions of a prototypical object: an object used as a template from which to get the initial properties for a new object. Any object can be associated as a prototype of another object, sharing its properties. In practice, you can look at this as a form of inheritance.
When you fetch a property on an object, JavaScript will search the
local object for the property. If it isn’t found, JavaScript will start
searching the object’s prototype and continue up the prototype tree,
eventually reaching Object.prototype
.
If the property is found, its value is returned; otherwise, undefined
will be returned.
In other words, if you start adding properties to Array.prototype
, they’ll be reflected across
every JavaScript array.
To subclass a class and inherit its properties, you need to first define a constructor function. Then, you need to assign a new instance of the parent class as the prototype for your constructor function. It looks like this:
var Animal = function(){}; Animal.prototype.breath = function(){ console.log('breath'); }; var Dog = function(){}; // Dog inherits from Animal Dog.prototype = new Animal; Dog.prototype.wag = function(){ console.log('wag tail'); };
Now, we can check to see whether the inheritance works:
var dog = new Dog; dog.wag(); dog.breath(); // Inherited property
Let’s add inheritance to our custom class library. We’ll pass through an optional parent class when creating a new class:
var Class = function(parent){ var klass = function(){ this.init.apply(this, arguments); }; // Change klass' prototype if (parent) { var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; }; klass.prototype.init = function(){}; // Shortcuts klass.fn = klass.prototype; klass.fn.parent = klass; klass._super = klass.__proto__; /* include/extend code... */ return klass; };
If a parent
is passed to the
Class
constructor, we make sure any
subclasses share the same prototype. This little dance around creating a
temporary anonymous function prevents instances from being created when a
class is inherited. The caveat here is that only instance properties, not
class properties, are inherited. There isn’t yet a cross-browser way of
setting an object’s __proto__
;.
Libraries like Super.js get around this
problem by copying the properties, rather than implementing proper dynamic
inheritance.
Now, we can perform simple inheritance by passing parent classes to
Class
:
var Animal = new Class; Animal.include({ breath: function(){ console.log('breath'); } }); var Cat = new Class(Animal) // Usage var tommy = new Cat; tommy.breath();
Like everything else in JavaScript, functions are just
objects. However, unlike other objects, they can be invoked. The context
inside the function—i.e., the value of this
—depends on where and how it’s
invoked.
Apart from using brackets, there are two other ways to invoke a
function: apply()
and call()
. The difference between them has to do with the arguments
you want to pass to the function.
The apply()
function takes two parameters: a
context and an array of arguments. If the context is null, the global
context is used. For example:
function.apply(this, [1, 2, 3])
The call()
function has exactly
the same behavior, yet it is used differently. The first argument is the
context, while each subsequent argument is delegated to the invocation. In
other words, you use multiple arguments—rather than an array like with
apply()
—to pass arguments to the
function.
function.call(this, 1, 2, 3);
Why would you want to change the context? This is a valid question because other languages get on fine without allowing explicit context changes. JavaScript uses context changes to share state, especially during event callbacks. (Personally, I feel this was a mistake in the design of the language, as it can be confusing for beginners and introduce bugs. However, it’s too late to change it now, so you need to learn how it works.)
jQuery takes advantage of apply()
and call()
throughout its API to change context—for
example, when using event handlers or iterating using each()
. This can be confusing at first, but it’s
useful when you understand what’s happening:
$('.clicky').click(function(){ // 'this' refers to the element $(this).hide(); }); $('p').each(function(){ // 'this' refers to the current iteration $(this).remove(); });
To access the original context, a common pattern stores the value of
this
in a local variable. For
example:
var clicky = { wasClicked: function(){ /* ... */ }, addListeners: function(){ var self = this; $('.clicky').click(function(){ self.wasClicked() }); } }; clicky.addListeners();
However, we can use apply
to make
this much cleaner, wrapping the callback within another anonymous
function, which preserves the original context:
var proxy = function(func, thisObject){ return(function(){ return func.apply(thisObject, arguments); }); }; var clicky = { wasClicked: function(){ /* ... */ }, addListeners: function(){ var self = this; $('.clicky').click(proxy(this.wasClicked, this)); } };
So, in the above example, we specify the context to be used inside
the click callback; the context jQuery invokes the function in is ignored.
In fact, jQuery’s API includes something to do just this—you guessed it,
jQuery.proxy()
:
$('.clicky').click($.proxy(function(){ /* ... */ }, this));
There are other useful reasons to use apply()
and call()
, such as delegating. We can delegate
calls from one function to another, and even alter the passed
arguments:
var App { log: function(){ if (typeof console == "undefined") return; // Turn arguments into a proper array var args = jQuery.makeArray(arguments); // Insert a new argument args.unshift("(App)"); // Delegate to the console console.log.apply(console, args); } };
Above, we’re making an array of arguments and then adding our own.
Finally, the call is delegated to console.log()
. If you’re not familiar with the
arguments
variable, it’s set by the interpreter and contains an array of
arguments with which the current scope was called. It’s not a true array
though—for example, it’s not mutable—so we have to convert it to something
usable with jquery.makeArray()
.
The proxy function described in the previous section is such a useful pattern that we should add it to our class library. We’ll add a proxy function on both classes and instances, allowing us to keep the class’ scope when handing functions off to event handlers and the like:
var Class = function(parent){ var klass = function(){ this.init.apply(this, arguments); }; klass.prototype.init = function(){}; klass.fn = klass.prototype; // Adding a proxy function klass.proxy = function(func){ var self = this; return(function(){ return func.apply(self, arguments); }); } // Add the function on instances too klass.fn.proxy = klass.proxy; return klass; };
We can now use the proxy()
function to wrap up functions, making sure they’re invoked in the right
scope:
var Button = new Class; Button.include({ init: function(element){ this.element = jQuery(element); // Proxy the click function this.element.click(this.proxy(this.click)); }, click: function(){ /* ... */ } });
If we didn’t wrap the click()
callback with a proxy, it would be called within the context of
this.element
, rather than Button
, causing all sorts of problems. A new
specification of JavaScript—ECMAScript,
5th Edition (ES5)—has also added support for controlling
invocation scope with the bind()
function. bind()
is called on a
function, making sure the function is called in the context of the
specified this
value. For
example:
Button.include({ init: function(element){ this.element = jQuery(element); // Bind the click function this.element.click(this.click.bind(this)); }, click: function(){ /* ... */ } });
This example is equivalent to our proxy()
function, and it makes sure the click()
function is called with the correct
context. Older browsers don’t support bind()
but, luckily, support can be shimmed
easily and implemented manually if needed. A shim basically implements a
compatibility layer on legacy browsers, directly extending the relevant
object’s prototypes, allowing you to use features of ES5 today without
worrying about older browsers. For example, a shim that would support
bind()
would look like this:
if ( !Function.prototype.bind ) { Function.prototype.bind = function( obj ) { var slice = [].slice, args = slice.call(arguments, 1), self = this, nop = function () {}, bound = function () { return self.apply( this instanceof nop ? this : ( obj || {} ), args.concat( slice.call(arguments) ) ); }; nop.prototype = self.prototype; bound.prototype = new nop(); return bound; }; }
Function
’s prototype is only
overwritten if the feature doesn’t already exist: newer browsers will
continue to use their native implementations. Shimming is especially
useful for arrays, which have had a bunch of new features added in recent
JavaScript versions. I personally use the es5-shim project
because it covers as many of the new features in ES5 as possible.
So far, any property we’ve added to our classes has been open to the world and can be changed at any time. Let’s now explore how to add private properties to our classes.
A lot of developers end up prefixing private properties with an underscore (_). Although these can still be changed, it makes it obvious that they’re part of a private API. I try to steer clear of this approach because it looks rather ugly.
JavaScript does have support for immutable properties; however, this isn’t implemented across the main browsers, so we’ll have to wait before using this method. Instead, we’ll use JavaScript anonymous functions to create a private scope, which can only be accessed internally:
var Person = function(){}; (function(){ var findById = function(){ /* ... */ }; Person.find = function(id){ if (typeof id == "integer") return findById(id); }; })();
We’re wrapping all our class’ properties in an anonymous function,
then creating local variables (findById
), which can only be accessed in the
current scope. The Person
variable is defined in the
global scope, so it can be accessed from anywhere.
Never define a variable without using the var
operator, since it always creates a global variable. If you need to
define a global variable, do so in the global scope or as a property on
window
:
(function(exports){ var foo = "bar"; // Expose variable exports.foo = foo; })(window); assertEqual(foo, "bar");
As with a lot of concepts in this book, it’s good to
understand the theory behind classes, but often in practice, you’ll use a
library. jQuery doesn’t include class support natively, but it can easily
be added with a plug-in like HJS. HJS lets you define classes by passing a set of properties
to $.Class.create
:
var Person = $.Class.create({ // constructor initialize: function(name) { this.name = name; } });
To inherit classes, pass their parent as an argument when creating them:
var Student = $.Class.create(Person, { price: function() { /* ... */ } }); var alex = new Student("Alex"); alex.pay();
To add class properties, set them directly on the class:
Person.find = function(id){ /* ... */ };
HJS’ API also includes a few utility functions, such as clone()
and equal(
)
:
var alex = new Student("Alex"); var bill = alex.clone(); assert( alex.equal(bill) );
HJS isn’t your only option; Spine also has a class implementation. To use it, just include spine.js in the page:
<script src="http://maccman.github.com/spine/spine.js"> </script> <script> var Person = Spine.Class.create(); Person.extend({ find: function() { /* ... */ } }); Person.include({ init: function(atts){ this.attributes = atts || {}; } }); var person = Person.init(); </script>
Spine’s class library has a similar API to the library we’ve been
building throughout this chapter. Use extend()
to add class properties and include()
to add instance properties. To inherit
from them, pass parent classes to the Spine.Class
instantiator.
If you’re widening your gaze beyond jQuery, Prototype is definitely worth checking out. It has an excellent class API that was the inspiration for a lot of other libraries.
jQuery’s John Resig has an interesting post on implementing classical inheritance with the library. It’s well worth reading, especially if you’re interested in the nitty-gritty behind the JavaScript prototype system.
Get JavaScript Web Applications now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.