Object-Oriented JavaScript with Backbone Classes

In this Article by Jeremy Walker, author of the book Backbone.js Essentials, we will explore the following topics:

  • The differences between JavaScript's class system and the class systems of traditional object-oriented languages
  • How new, this, and prototype enable JavaScript's class system
  • Extend, Backbone's much easier mechanism for creating subclasses

(For more resources related to this topic, see here.)

JavaScript's class system

Programmers who use JavaScript can use classes to encapsulate units of logic in the same way as programmers of other languages. However, unlike those languages, JavaScript relies on a less popular form of inheritance known as prototype-based inheritance. Since Backbone classes are, at their core, just JavaScript classes, they too rely on the prototype system and can be subclassed in the same way as any other JavaScript class.

For instance, let's say you wanted to create your own Book subclass of the Backbone Model class with additional logic that Model doesn't have, such as book-related properties and methods. Here's how you can create such a class using only JavaScript's native object-oriented capabilities:

// Define Book's Initializer
var Book = function() {
// define Book's default properties
this.currentPage = 1;
this.totalPages = 1;
}
// Define book's parent class
Book.prototype = new Backbone.Model();
// Define a method of Book
Book.prototype.turnPage = function() {
this.currentPage += 1;
return this.currentPage;
}

If you've never worked with prototypes in JavaScript, the preceding code may look a little intimidating. Fortunately, Backbone provides a much easier and easier to read mechanism for creating subclasses. However, since that system is built on top of JavaScript's native system, it's important to first understand how the native system works. This understanding will be helpful later when you want to do more complex class-related tasks, such as calling a method defined on a parent class.

The new keyword

The new keyword is a relatively simple but extremely useful part of JavaScript's class system. The first thing that you need to understand about new is that it doesn't create objects in the same way as other languages. In JavaScript, every variable is either a function, object, or primitive, which means that when we refer to a class, what we're really referring to is a specially designed initialization function. Creating this class-like function is as simple as defining a function that modifies this and then using the new keyword to call that function.

Normally, when you call a function, its this is obvious. For instance, when you call the turnPage method of a book object, the this method inside turnPage will be set to this book object, as shown here:

var simpleBook = {currentPage: 3, pages: 60};
simpleBook.turnPage = function() {
this.currentPage += 1;
return this.currentPage;
}
simpleBook.turnPage(); // == 4

Calling a function that isn't attached to an object (in other words, a function that is not a method) results in this being set to the global scope. In a web browser, this means the window object:

var testGlobalThis = function() {
alert(this);
}
testGlobalThis(); // alerts window

When we use the new keyword before calling an initialization function, three things happen (well, actually four, but we'll wait to explain the fourth one until we explain prototypes):

  • JavaScript creates a brand new object ({})for us
  • JavaScript sets the this method inside the initialization function to the newly created object
  • After the function finishes, JavaScript ignores the normal return value and instead returns the object that was created

As you can see, although the new keyword is simple, it's nevertheless important because it allows you to treat initialization functions as if they really are actual classes. At the same time, it does so without violating the JavaScript principle that all variables must either be a function, object, or primitive.

Prototypal inheritance

That's all well and good, but if JavaScript has no true concept of classes, how can we create subclasses? As it turns out, every object in JavaScript has two special properties to solve this problem: prototype and __proto__ (hidden). These two properties are, perhaps, the most commonly misunderstood aspects of JavaScript, but once you learn how they work, they are actually quite simple to use.

When you call a method on an object or try to retrieve a property JavaScript first checks whether the object has the method or property defined in the object itself. In other words if you define a method such as this one:

book.turnPage = function()
this.currentPage += 1;
};

JavaScript will use that definition first when you call turnPage.

In real-world code, however, you will almost never want to put methods directly in your objects for two reasons. First, doing that will result in duplicate copies of those methods, as each instance of your class will have its own separate copy. Second, adding methods in this way requires an extra step, and that step can be easily forgotten when you create new instances.

If the object doesn't have a turnPage method defined in it, JavaScript will next check the object's hidden __proto__ property. If this __proto__ object doesn't have a turnPage method, then JavaScript will look at the __proto__ property on the object's __proto__. If that doesn't have the method, JavaScript continues to check the __proto__ of the __proto__ of the __proto__ and keeps checking each successive __proto__ until it has exhausted the chain.

This is similar to single-class inheritance in more traditional object-oriented languages, except that instead of going through a class chain, JavaScript instead uses a prototype chain. Just as in an object-oriented language we wind up with only a single copy of each method, but instead of the method being defined on the class itself, it's defined on the class's prototype.

In a future version of JavaScript (ES6), it will be possible to work with the __proto__ object directly, but for now, the only way to actually see the __proto__ property is to use your browser's debugging tool (for instance, the Chrome Developer Tools debugger):

 

This means that you can't use this line of code:

book.__proto__.turnPage();

Also, you can't use the following code:

book.__proto__ = {
turnPage: function() {
this.currentPage += 1;
}
};

But, if you can't manipulate __proto__ directly, how can you take advantage of it? Fortunately, it is possible to manipulate __proto__, but you can only do this indirectly by manipulating prototype. Do you remember I mentioned that the new keyword actually does four things? The fourth thing is that it sets the __proto__ property of the new object it creates to the prototype property of the initialization function. In other words, if you want to add a turnPage method to every new instance of Book that you create, you can assign this turnPage method to the prototype property of the Book initialization function, For example:

var Book = function() {};
Book.prototype.turnPage = function() {
this.currentPage += 1;
};
var book = new Book();
book.turnPage();// this works because book.__proto__ == Book.
prototype

Since these concepts often cause confusion, let's briefly recap:

  • Every object has a prototype property and a hidden __proto__ property
  • An object's __proto__ property is set to the prototype property of its constructor when it is first created and cannot be changed
  • Whenever JavaScript can't find a property or method on an object, it "checks each step of the __proto__ chain until it finds one or until it runs "out of chain

Extending Backbone classes

With that explanation out of the way, we can finally get down to the workings of Backbone's subclassing system, which revolves around Backbone's extend method. To use extend, you simply call it from the class that your new subclass will be based on, and extend will return the new subclass. This new subclass will have its __proto__ property set to the prototype property of its parent class, allowing objects created with the new subclass to access all the properties and methods of the parent class. Take an example of the following code snippet:

var Book = Backbone.Model.extend();
// Book.prototype.__proto__ == Backbone.Model.prototype;
var book = new Book();
book.destroy();

In the preceding example, the last line works because JavaScript will look up the __proto__ chain, find the Model method destroy, and use it. In other words, all the functionality of our original class has been inherited by our new class.

But of course, extend wouldn't be exciting if all it can do is make exact clones of the parent classes, which is why extend takes a properties object as its first argument. Any properties or methods on this object will be added to the new class's prototype. For instance, let's try making our Book class a little more interesting by adding a property and a method:

var Book = Backbone.Model.extend({
currentPage: 1,
turnPage: function() {
this.currentPage += 1;
}
});
var book = new Book();
book.currentPage; // == 1
book.turnPage(); // increments book.currentPage by one

The extend method also allows you to create static properties or methods, or in other words, properties or methods that live on the class rather than on objects created from that class. These static properties and methods are passed in as the second classProperties argument to extend. Here's a quick example of how to add a static method to our Book class:

var Book = Backbone.Model.extend({}, {
areBooksGreat: function() {
alert("yes they are!");
}
});
Book.areBooksGreat(); // alerts "yes they are!"
var book = new Book();
book.areBooksGreat(); // fails because static methods must be
called on a class

As you can see, there are several advantages to Backbone's approach to inheritance over the native JavaScript approach. First, the word prototype did not appear even once in any of the previously mentioned code; while you still need to understand how prototype works, you don't have to think about it just to create a class. Another benefit is that the entire class definition is contained within a single extend call, keeping all of the class's parts together visually. Also, when we use extend, the various pieces of logic that make up the class are ordered the same way as in most other programming languages, defining the super class first and then the initializer and properties, instead of the other way around.

Summary

In this article, we explored how JavaScript's native class system works and how the new, this, and prototype keywords/properties form the basis of it. We also learned how Backbone's extend method makes creating new subclasses much more convenient as well as how to use apply and call to invoke parent methods (or when providing callback functions) to preserve the desired this method.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Backbone.js Essentials

Explore Title