Chapter 4. Classes

In the previous chapter, we learned about JavaScript's object implementation and discovered how ideas from both prototypal and classical languages were incorporated into the language. This merging of approaches is somewhat unique to the language and sometimes makes it harder for new developers to learn how object-oriented programming in JavaScript works.

MooTools, with its focus on modularity and simplicity, provides an elegant alternative to the native implementation. To put it succinctly, MooTools turns JavaScript into a classical language. In this chapter, we'll learn how this class system works, and we'll examine some of the techniques used by MooTools to add a major language feature to JavaScript.

From Prototypes to Classes

Suppose we need to create several JavaScript objects to represent people. These objects need to have two properties, name and age, and a method called log to print out the values of these properties. Recalling what we learned from the last chapter, we come up with this snippet:

var Person = function(name, age){
    this.name = name;
    this.age = age;
};

// Properties
Person.prototype.name = '';
Person.prototype.age = 0;

// Methods
Person.prototype.log = function(){
    console.log(this.name + ', ' + this.age);
};

// Creating a new Person instance
var mark = new Person("Mark", 23);
mark.log(); // 'Mark, 23'

First, we created a Person constructor by assigning a function to our variable. We then defined the name, age, and log members by augmenting our constructor's prototype object. Finally, we created a new instance object using new Person(). We know that the new keyword creates a new object that inherits from Person.prototype, and we stored our newly created object in our mark variable.

So the whole process—without thinking too much of the details—can be summed up in three steps: create a constructor function, modify its prototype property, and instantiate it using new. Of course, this is from a JavaScript perspective—in true prototypal languages (like our Io example in the last chapter), the process is much simpler: create an object, and clone it to create a new object.

Unlike their prototypal counterparts, though, classical programming languages don't use objects to define other objects but instead rely on a special construct called a class to define objects. A class—much like a prototype—defines the structure of an object by specifying its properties and methods. For example, here's an implementation of a Person class in a classical language called ooc:

Person: class {

    // Properties
    name := ""
    age  := 0


    // Methods
    init: func (name: String, age: Int) {
        this name = name
        this age = age
    }

    log: func {
        printf("%s, %d", this name, this age)
    }

}

// Creating a new Person instance
mark := Person new("Mark", 23)
mark log() // "Mark, 23"

First, we created a new class called Person and then we defined several members: the properties name and age, which are a string and an integer respectively, and two methods, init and log. We then created a new object instance of Person called mark and called its log() to print out the name and age of our instance. You'll notice that we didn't have a constructor function, although we did have an init method that looks similar to our original constructor. We'll learn more about this difference later. The important thing to note right now is that both our JavaScript and ooc examples are doing the same task of defining objects to represent people, even though they're using different constructs to do it.

While they are both defining the structure of objects, classes and prototypes are different within the languages themselves. In a true prototypal language (which JavaScript is not), prototypes are always objects and any object can be a prototype. In contrast, classes can be objects or primitive types, depending on the language implementation. Classical languages also use special syntax to define classes, as in our ooc example, while prototype languages define prototypes like regular objects. This special syntax for classes makes the structure of instances immutable in most cases: you won't be able to add new members to objects without defining them first through the class—something you can freely do in prototypal language. For instance, you won't be able to do mark occupation := 'JavaScript Developer' in our ooc example above because the property occupation wasn't defined in our class.

But the biggest difference is in terms of inheritance and types. Objects in classical languages inherit from classes and, because the word class implies a group of things, these objects are seen not only as instances of those classes but also members of that group defined by class. In the case of our ooc example, the object mark not only inherits from the Person class but it's also a member of a group that is composed of all Person objects. We commonly refer to this grouping as a type. It's the job of a class not only to define the structure of objects but also define the type of those objects.

However, we can't have the same grouping in true prototypal languages. Since objects are cloned and inherit directly from other objects, there's no separate construct that defines the type of the objects. For example, we have a Person object and we clone it to create a mark object. We can say that the mark object inherits from Person and that it's a copy of the Person object. But this doesn't mean that the mark object is of the type Person because Person itself is just an object and it doesn't define a grouping.

So where does JavaScript stand in this? Interestingly, it's somewhere in the middle. JavaScript is technically a prototypal language but it also contains classical influences. For one, a JavaScript object doesn't inherit from just any plain object but from the special prototype object defined as a property of the constructor. In essence, JavaScript prototypes are similar to classes because they're special language constructs. The existence of these special language constructs—prototypes and constructor functions—means we could have "types" in JavaScript because they create groupings for our objects. But because the prototype is still an object, JavaScript still implements prototypal rather than classical inheritance.

The MooTools Class System

Take a look at the previous JavaScript and ooc examples. Clearly, the class declaration in ooc is cleaner than the declarations in JavaScript. In our ooc example, all the properties and methods of our object were declared inside the class {...} declaration and you could easily see what members our instances would have. Our JavaScript example, on the other hand, is more verbose and repetitive: there's no visual grouping for our constructor and prototype members and we had to write Person.prototype several times.

This becomes even more apparent as you start writing large JavaScript applications where you need to declare many different kinds of objects. I don't know about you, but typing stuff over and over again seems counterproductive and tedious to me.

Developers were quick to see this problem and they developed many techniques to simplify object-oriented programming in JavaScript. The best solution, of course, was to implement a class system in JavaScript. There are many class systems available from different frameworks, each with its own feature set, but arguably the best among all of them is the MooTools class system.

That's quite a big assertion on my part, so let me give you a few reasons:

  • The syntax of MooTools classes is similar to what's found in actual classical languages.

  • The MooTools class system is built on the native JavaScript object implementation—all the magic is already in the language itself.

  • The class system abstracts and extends the native object implementation, adding useful features that simplify object-oriented programming.

  • Inheritance is handled by the class itself—including direct inheritance and "multiple inheritance" using "mixins" (more on these later and in the next chapter).

  • The system itself is extensible, which means you can add new features easily.

We'll discuss these points more thoroughly throughout this chapter, but here's the most important thing to consider: MooTools doesn't give you a half-baked class system that's nothing more than syntax changes. Instead, the MooTools class system is a complete implementation of class-based programming for JavaScript.

The first point is actually easy to prove. To give you a taste, here's how we implement the previous examples using a MooTools class:

var Person = new Class({

    // Properties
    name: '',
age: 0,

    // Methods
    initialize: function(name, age){
        this.name = name;
        this.age = age;
    },

    log: function(){
        console.log(this.name + ', ' + this.age);
    }

});

// Creating a new Person instance
var mark = new Person('Mark', 23);
mark.log(); // 'Mark, 23'

Pretty, isn't it? In fact, it looks strikingly similar to our ooc example. But even with the syntax change, using the class is no different from how we'd use a constructor: we still used the new keyword, we still passed arguments to our constructor function, and we still got a new object that inherits from Person.

In MooTools, we create a class using the Class constructor. This constructor takes a single object argument called params, the key-value pairs that will become the members of the class. The object passed to the class constructor is your blueprint, and it defines how you want your instances to look. It then returns a constructor function. Let's take a look at another class:

var Animal = new Class({

    name: 'Animal',

    eat: function(){
        console.log(this.name + ' is eating.'),
    },

    walk: function(){
        console.log(this.name + ' is walking.'),
    }

});

var myAnimal = new Animal();
myAnimal.eat(); // 'Animal is eating.'
myAnimal.walk(); // 'Animal is walking.'

console.log(myAnimal instanceof Animal); // true

Here we create a new class called Animal by passing an object to the Class constructor. Our object literal has three members: a property called name and two methods, eat and walk. The Class constructor returns a new constructor function and then we create an instance of the class called myAnimal using new Animal(). Because we declared the eat and walk methods for our class using the object literal, we are able to call these methods on our myAnimal object. Finally, we use the instanceof operator to verify that our new myAnimal object is indeed an instance of Animal.

I mentioned that the MooTools class system is built upon the native object implementation, and we saw that the Class constructor takes an object that describes the members of the class we're creating and then returns a constructor. If we connect these two pieces of information, we get the gist of how the Class constructor works: it creates a new constructor function, goes through the keys we specified in the object argument, appends these keys to the new constructor's prototype property, and returns the constructor.

It might sound too simple, but that's exactly how the core of the MooTools class system works. A MooTools class is nothing more than a simple abstraction of the constructor-prototype pair with additional extensions to make working with them simpler. This means that everything we learned in the previous chapter about objects and prototypes still applies to classes.

Of course there are major differences, particularly in how we'll look at things from now on. Because MooTools abstracts the native prototypal implementation, there's almost no need for us to work directly with prototypes—and in most cases, you won't even notice you're working with prototypes.

Another thing we have to change is how we talk about object-oriented programming itself. Since we'll be dealing with classes rather than prototypes, we'll be using classical terms from here on. It's a weird shift to talk about JavaScript in a classical manner, but because MooTools provides a complete system for classical programming, we'll need to use proper terms when working with the class system.

Constructors and Initializers

In the MooTools class system, there's no distinction between the constructor and the prototype because we have only one blueprint object: the class. Before, we had to create a constructor function and then augment its prototype property to add new methods. But since we're only going to deal with classes from now on, we'll only have to worry about the class itself and the members we'll be defining in our class.

One shift we'll have to make when switching from the prototypal model to the classical one is to forget about the actual constructor function. In the prototypal model, we used the constructor function to prepare the object. In the case of our Person constructor, we set the name property of the instance to the value of the argument passed. When it comes to more complex programs, constructor functions tend to be more complex than this, performing lots of operations that set up the new instance.

But we didn't declare a constructor function in our implementation of the Person class using the MooTools class system. Instead, the stuff we usually place inside the constructor's body was placed in a special method called initialize in our class declaration. This method is called the initializer and it effectively replaces the constructor function from the prototypal model.

In essence, both the constructor and the initializer are used for the same thing: to set up the new instance. The main difference is that the constructor in the prototypal model is technically separate from its prototype and is not inherited by its instances, while an initializer is an actual method that's inherited by objects:

var Person = new Class({

    // Properties
    name: '',
    age: 0,

    // Methods
    initialize: function(name, age){
        this.name = name;
        this.age = age;
    },

    log: function(){
        console.log(this.name + ', ' + this.age);
    }
});

var mark = new Person('Mark', 23);
console.log(typeof mark.initialize); // 'function'

console.log(typeof Person); // 'function'
console.log(typeof Person.prototype.initialize); // 'function'

However, having an initialize method doesn't mean we don't have an actual constructor. In our Person class example, Person is our constructor and Person.prototype.initialize is the initializer. Since the MooTools class system still uses the native prototype-based object implementation, we still need constructor functions to augment their prototype properties and to create new objects using the new keyword.

One misconception is that the initialize function we declare in our class will become the constructor function for the class, but this isn't the case. All classes actually use a common constructor function. We say "common constructor" not because all classes use the exact same function, but because all of them have a constructor that looks like this:

// the common constructor function
function(){
    reset(this);
    if (newClass.$prototyping) return this;
    this.$caller = null;
    var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
    this.$caller = this.caller = null;
    return value;
}

Most of the stuff inside this common constructor might look like gibberish right now, but for the moment let's focus on the line that starts with var value. This is the most important line in the common constructor and it invokes the instance's initialize method, passing any arguments given to the common constructor. Also, note that the return value of the initialize method is stored in the variable value and returned at the end of our common constructor.

Applying this to our previous example, the call to new Person('Mark', 23) first invoked the Person function (which is the common constructor) and then the instance's initialize method was called, passing the argument 'Mark' to it. These invocations are transparent: when looking at it from the outside, it seems that the initialize method was invoked directly.

The separation of the constructor function and the initialize method might seem redundant—after all, we could just remove the common constructor and use the initialize function as the constructor itself. This distinction, however, is important because of the difference between instantiation and initialization.

Instantiation is the process of creating a new object, while initialization is the process of setting up the object. With our original prototypal model, both the instantiation and initialization phases happen: first instantiation via the new keyword and then initialization by calling the constructor.

But there are times when we want to create an object without initializing it. It's probably hard to think of why we'd want to do this, but we'll see how it's useful later on. What we do need to realize right now is that with our original prototypal model, we can't separate instantiation and initialization, because the constructor always gets called after creating a new object. Having a separate method for initialization solves this problem because we can now bypass the initialization phase if necessary.

The common constructor also enforces some workarounds for the language, like removing object references and adding private methods. Since we already have a function automating these tasks for us, we can focus our attention on the class itself rather than having to write these workaround codes every time.

Rethinking Members

In the prototypal model, we had to worry about the separation of the constructor and the prototype. While the constructor is still invoked to process the instance, it's not part of the instance itself—which can lead to some confusion later on. In contrast, the MooTools class system eliminates this distinction by making the constructor "irrelevant" and instead turning to the initialize method. With a class, the only things you have to worry about are the actual members of your object.

The MooTools class system still uses the native prototypal implementation, and members are still added to the prototype of the constructor of the class. Because properties and methods still behave the same way, we can easily apply what we already learned about them from the last chapter.

The process of adding members to the prototype of the class isn't straightforward, though. It may seem that the Class constructor simply takes the members of the object argument passed and adds them directly to the prototype, but there's a more complex process happening underneath.

Before we go any further, let's revisit a problem we encountered in the last chapter. Suppose we have the following class:

// person.js
var Person = new Class({

    name: '',
    age: 0,

    initialize: function(name, age){
        this.name = name;
        this.age = age;
    },

    log: function(){
        console.log(this.name + ', ' + this.age);
    }

});

This class is found in a file called person.js, which is an external file that's not accessible to us. Now we want to add new methods to this class: getName, setName, getAge, and setAge. We don't have access to the class declaration itself, so we can't edit it directly. What we did in the last chapter was to augment Person.prototype to add the new members. Since classes use the native prototypal implementation, we could use the same technique to augment our Person class.

That also works with classes, but it's not considered proper MooTools style. Classes abstract prototypes, so there's no need to directly augment the prototype object. The proper way is to use a class method known as implement to add these new members:

// program.js
Person.implement({

    getName: function(){
        return this.name;
    },

    setName: function(name){
        this.name = name;
    },

    getAge: function(){
return this.age;
    },

    setAge: function(age){
        this.age = age;
    }

});

We call the implement method of our Person class and then pass it an object literal containing the methods we want to add to our class. The result is that all instances of Person will now have those methods, just like the result we'd get if we augmented the prototype object directly.

All classes have the implement method: it's a part of the simplified abstraction of the MooTools class system. I said that it's not proper MooTools style to directly augment the prototype of a class, but that's an understatement. What I should have said was this: always use the implement method. Unless you know what you're doing and unless you have a real need to do so, never directly augment the prototype of a class.

Some might think that this is too strict a rule. If MooTools classes just abstract the native prototypal implementation, augmenting class prototypes should work. This isn't the case, though: part of the abstraction provided by classes is ensuring proper object behavior. The implement method doesn't just add members to the prototype directly, but instead processes the members first to make sure that everything will work as expected.

You might have noticed that the implement syntax is similar to the Class constructor's syntax, with both requiring an object literal describing the properties and methods to be added to the prototype of the class. That's because the Class constructor itself uses implement to add the members. All class members go through the implement method—even the ones passed through the Class constructor.

The implement method looks through each of the items from the object argument and checks whether it should process that particular item before it adds it to the prototype. The processing depends on the type of the item: properties undergo different processing from methods, and there is special processing done to items with particular keys. Since we're talking about members now, we won't go into detail about those keys here; instead we'll focus on how implement processes methods and properties.

Rethinking Methods

In the prototypal model, adding methods is done simply by adding a property to the prototype with a function value:

var myFn = function(){
    console.log('Hello'),
};

var Person = function(){};
Person.prototype.log = myFn;

var mark = new Person();

console.log(typeof mark.log); // 'function'
console.log(mark.log == myFn); // true

mark.log(); // 'Hello'

Here, we assign the function that's stored in the myFn variable to Person.prototype.log. Because functions are referenced, both myFn and Person.prototype.log now point to a single function, which we confirm by comparing myFn and mark.log. No surprises here. However, the behavior is different with classes:

var myFn = function(){
    console.log('Hello'),
};

var Person = new Class({
    log: myFn
});

var mark = new Person();

console.log(typeof mark.log); // 'function'
console.log(mark.log == myFn); // false

mark.log(); // 'Hello'

We know that the log method is our original myFn function, because mark.log() outputs as expected. But when we compare mark.log and myFn, we're told that they're not the same function. This means that myFn and Person.prototype.log point to different functions. But how come mark.log() still works as if both functions are the same?

This is one of the changes brought about by the implement method. We learned about function decoration in Chapter 2; well, the implement method decorates all methods in a class. All class methods you pass to implement are changed to the following decorated function:

function(){
    if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.'),
    var caller = this.caller, current = this.$caller;
    this.caller = current; this.$caller = wrapper;
    var result = method.apply(this, arguments);
    this.$caller = current; this.caller = caller;
    return result;
}

Don't get scared yet—right now you can ignore most of the lines in this extremely confusing function. The most important line here is the one that starts with var result. The identifier method in this function points to the actual method you passed in your declaration. In the case of our previous example, method points to the same function as myFn. Because it's simply a decorator, calling this function also invokes the original function. The results of your original function are then stored in a variable and returned. The whole process is transparent, and you won't notice what's happening when you're working with classes.

This decorated function also stores the original method in a special property called $origin. We can compare the methods as follows:

var myFn = function(){
    console.log('Hello'),
};

var Person = new Class({
    log: myFn
});

var mark = new Person();
console.log(typeof mark.log); // 'function'
console.log(mark.log == myFn); // false

console.log(mark.log.$origin == myFn); // true

mark.log(); // 'Hello'

Arguably, this function seems like the most complex of all functions in MooTools, and it does take a while to understand it. This complex decoration might be overwhelming right now, but we'll learn how it works throughout this chapter and the next. While understanding how this function works internally isn't essential to working with classes, it'll help you see how MooTools implements classical features like calling overridden methods and hiding private methods.

Rethinking Properties

If you look back at all the prototype-based examples we've seen so far, you'll notice that we never declared properties for most of them. Instead of augmenting them to the prototype itself, we created the properties inside the constructor. We did this so we wouldn't encounter this particular behavior:

var data = {name: '', age: 0};

var Person = function(name, age){
    this.data.name = name;
    this.data.age = age;
};

Person.prototype.data = data;

Person.prototype.log = function(){
    console.log(this.data.name + ', ' + this.data.age);
};

var mark = new Person('Mark', 23);
mark.log(); // 'Mark, 23'

data.name = 'Joseph';
data.age = 22;

mark.log(); // 'Joseph, 22'

console.log(data == Person.prototype.data); // true

We discussed why this problem occurs in the last chapter, but the basic explanation is that both data and Person.prototype.data point to the same object, so any changes to one are reflected in the other.

However, this approach isn't recommended when using classes. In fact, it's advised to declare all properties for classes when possible. But unlike our previous example, the problem doesn't come up when using classes, and changing the value of the data object doesn't affect the data property of our mark object:

var data = {name: '', age: 0};

var Person = new Class({
data: data,

    initialize: function(name, age){
        this.data.name = name;
        this.data.age = age;
    },

    log: function(){
        console.log(this.data.name + ', ' + this.data.age);
    }

});

var mark = new Person('Mark', 23);
mark.log(); // 'Mark, 23'

data.name = 'Joseph';
data.age = 22;

mark.log(); // 'Mark, 23'

console.log(data == Person.prototype.data); // false

This is another effect of using the implement method. Properties declared for a class undergo a process called dereferencing, which removes any reference from the object. This is done by creating a copy of an object that's exactly like the original. For instance, both the data and Person.prototype.data objects above might look like the same object, but the latter is really just a copy. Instead of augmenting it directly into the prototype, a copy of data was created and assigned to Person.prototype.data. Any references to data were removed, and changes to this object no longer affect the copy.

The dereferencing done by implement is recursive: if an object contains another object, the inner object is also dereferenced. In an array of objects, for example, not only is a copy of an array created, but each object inside the array is also copied. However, dereferencing is limited to only some kinds of objects: arrays, objects created using literals or new Object(), and objects created using user-defined constructors. Objects like functions, dates, and DOM objects aren't dereferenced since they're host objects, and primitives like strings and numbers don't need to be dereferenced because they're not reference types. For example, the date object won't be dereferenced in the following:

var date = new Date();

var Item = new Class({
    date: date
});

var obj = new Item();

console.log(obj.date == date); // true

Dereferencing, however, doesn't just happen on the class level: instances are also dereferenced. To understand why, we need to take a look at a variation of the original problem:

var Person = function(name, age){
    this.data.name = name;
    this.data.age = age;
};
Person.prototype.data = {name: '', age: 0};

Person.prototype.log = function(){
    console.log(this.data.name + ', ' + this.data.age);
};

var mark = new Person('Mark', 23);
mark.log(); // 'Mark, 23'

var joseph = new Person('Joseph', 22);
joseph.log(); // 'Joseph, 22'

mark.log(); // 'Joseph, 22'

console.log(mark.data == joseph.data); // true

This is an even bigger problem: because both mark and joseph inherit from Person.prototype, their data properties both point to the same object. Therefore, any changes made to one instance also affect all other instances. This is what we'd expect from objects, and if you recall our discussion in the previous chapter, we solved this by moving the data declaration into the constructor to give each instance its own separate data property.

The MooTools class system, on the other hand, solves this automatically for us:

var Person = new Class({

    data: {name: '', age: 0},

    initialize: function(name, age){
        this.data.name = name;
        this.data.age = age;
    },

    log: function(){
        console.log(this.data.name + ', ' + this.data.age);
    }

});

var mark = new Person('Mark', 23);
mark.log(); // 'Mark, 23'

var joseph = new Person('Joseph', 22);
joseph.log(); // 'Joseph, 22'

mark.log(); // 'Mark, 23'

console.log(mark.data == joseph.data); // false

The dereferencing here is no longer done by implement, but by the common constructor. If you look back at the code for the common constructor in the section on Constructors and Initializers, you'll see that the first line is a function call: reset(this). The this keyword in that code points to the newly created instance of the class, and the reset function is the private dereferencing function for class instances.

When we called new Person(), a new instance is created and then the Person constructor is called. The first thing the constructor does is to call reset to dereference the instance. This instance, of course, already inherits from Person.prototype, so the properties declared in our prototype are copied and reassigned in the object to give each instance its own copy. This automated process ensures that all our instances have their own copies of the objects declared in the prototype.

Inheritance

Because classes are just abstractions of the native prototypal implementation, instances still inherit from the prototype of their constructors, and the prototype chain is still traversed to look for members. Setting up the prototype chain, though, is different with classes.

In the last chapter, we were able to make objects that inherit from multiple prototypes by creating deliberate chains:

var Animal = function(name){
    this.name = name;
};

Animal.prototype.eat = function(){
    console.log('The ' + this.name + ' is eating.'),
};

var Cat = function(){};

Cat.prototype = new Animal('cat'),

Cat.prototype.meow = function(){
    console.log('Meow!'),
};

var cat = new Cat();
cat.eat(); // 'The cat is eating.'
cat.meow(); // 'Meow!'

Here we created a Cat constructor and then set its prototype to be an instance of Animal. Because of how the prototype chain works, all instances of Cat not only inherit from Cat.prototype but also from Animal.prototype. To set the deliberate chain, we had to assign an actual object as the prototype of the new constructor. This is the essence of prototypal inheritance: objects inherit from other objects.

Classical inheritance has a different view: objects—and classes themselves—inherit from classes. If we were to write the example above in a classical language, we would have to declare that the Cat class inherits from the Animal class. This inheritance is direct: the Cat class won't be inheriting from an instance of the Animal class, like in our JavaScript example, but from the Animal class itself. In classical terms, we then say that the Cat class is a subclass of the Animal class, and the Animal class is the superclass of the Cat class.

The MooTools class system provides classical inheritance with a simple interface. When we implement the example above using classes, we get the following:

var Animal = new Class({

    name: '',

    initialize: function(name){
        this.name = name;
},

    eat: function(){
        console.log('The ' + this.name + ' is eating.'),
    }

});

var Cat = new Class({

    Extends: Animal,

    initialize: function(){
        this.name = 'cat';
    },

    meow: function(){
        console.log('Meow!'),
    }

});

var cat = new Cat();
cat.eat(); // 'The cat is eating.'
cat.meow(); // 'Meow!'

As you've probably guessed, the important line in this snippet is Extends: Animal. Extends is one of the special keywords processed by the implement method, and it's actually connected to a function that we'll talk about more in the next chapter. The important point to know right now is that this line declares that our Cat class is a subclass of Animal, and sets the prototype chain of the Cat class to inherit from Animal.

Note

Extends, like other special keywords, is case-sensitive. Always remember to capitalize the first letter.

Unlike our prototypal example, we passed Animal in our declaration instead of new Animal(). Extends allows us to declare inheritance using classes themselves instead of instances of those classes. Aside from being easier to write, this has the added benefit of being easier to read: a quick glance at our Cat class informs us that it's a subclass of Animal because we can easily see the Extends declaration.

The Extends inheritance mechanism is another MooTools abstraction. Since we're still using prototypes beneath the class system, JavaScript expects our prototype chain to be declared using the prototypal model. Extends does this automatically for us: when the implement method sees the Extends keyword, it creates a new instance of the class we declared and uses this new instance to set up the prototype chain.

In our example above, implement goes through each of the items in our object literal to build the Cat class. When it encounters Extends: Animal, it creates a new instance of the class by instantiating new Animal(). It then sets the prototype of the common constructor to the new Animal instance and adds the other members of the class to the prototype. Finally, the Class constructor returns the common constructor and it becomes the final Cat class.

In our section on the common constructor, we found out that the class system distinguishes between instantiation and initialization. I also said that there are times we would want to create instances without initializing them. Extends is one of those times: the instance of the superclass created via Extends isn't initialized:

var Superclass = new Class({

    initialize: function(name){
        console.log('Initializing Superclass.'),
    }

});

var Subclass = new Class({

    Extends: Superclass,

    initialize: function(){
        console.log('Initializing Subclass.'),
    }

});

var obj = new Subclass(); // 'Initializing Subclass.'

In this code, we get only the 'Initializing Subclass' log in our console. When Subclass was created, an instance of Superclass was created to become the value of Subclass.prototype. But this instance of Superclass wasn't initialized, which we confirm because there's no log in the console.

This behavior happens because of how initializers work. It's the job of the initializer to prepare an instance and to set it up in a way that's unique to the instance. Often, this is done using special arguments that are passed when creating an instance. Take a look at the following snippet:

var Postable = function(element){
    this.element = element;
    this.element.method = 'POST';
};

When we create instances of Postable, we are expected to pass an element argument to the constructor. The instance is then initialized by setting the value of its element member to the element argument we passed, then a new property called method is added to the member. A problem occurs, though, when we try to create a subclass:

var Postable = function(element){
    this.element = element;
    this.element.method = 'POST';
};

var SubPostable = function(){};

SubPostable.prototype = new Postable(); // this will throw an error

To make SubPostable a subclass of Postable, we had to declare SubPostable.prototype to be a new instance of Postable. But since Postable had a combined constructor and initializer, we get an error when we create the instance without passing an element argument.

Of course, we can simply pass an argument when we create the new instance, but what argument should we pass? Remember that during the process of subclassing, we don't yet know which arguments are going to be passed—those items will be available when we instantiate the class. We could pass a dummy object, but doing so just to bypass an error isn't good programming.

On the other hand, we'll never run into such a problem with the MooTools class system because constructors and initializers are separate. Even though the class system creates an instance of Postable and sets it as the prototype of Subpostable, the initialized method of the Postable instance never gets called:

var Postable = new Class({

    element: null,

    initialize: function(element){
        this.element = element;
        this.element.method = 'POST';
    }

});

var SubPostable = new Class({

    Extends: Postable

});

To fully understand why MooTools does this, we need to think back to the prototype chain. We create deliberate chains in order to create extensions and variations of our original prototypes. An Animal prototype, for instance, should contain members that are shared by all animals, such name, eat(), and sleep(). It could then be specialized to make Cat, Bird, or Dog prototypes, each with its own set of methods that are unique to cats, birds, or dogs. These specializations could be further divided into specific types of cats, birds, or dogs. The important thing to consider is that we're doing this in order to inherit members from all these classes without having to rewrite members again: an instance of Magpie, for example, might inherit properties and methods from Magpie.prototype, Bird.prototype and Animal.prototype.

JavaScript's object implementation requires us to declare a prototype as an instance of another prototype in order to create a deliberate chain. Unfortunately, the use of constructor functions as initializers in the prototypal model works against us, since the processes of instantiation and initialization are merged into one function. If the only reason we set the prototype to an instance of another prototype is to inherit members, we shouldn't need to initialize the instance into a usable state, because we're not going to use it except for traversal.

This concern is handled properly by the MooTools class system: because we only need to instantiate the superclass in order to access its properties and methods for our subclass, the superclass is instantiated without being initialized. When we need to finally initialize our object, we can do so in the subclass itself without having to worry about how the superclass handles initialization.

Overridden Methods

The prototype chain is invisible: there's no direct way to check whether a prototype inherits from another object. MooTools classes, on the other hand, have visible links to their superclass, which is called parent:

var Animal = new Class({

    name: '',
initialize: function(name){
        this.name = name;
    }

});

var Cat = new Class({

    Extends: Animal,

    initialize: function(){
        this.name = 'cat';
    }

});

console.log(typeof Animal.parent); // 'undefined'
console.log(typeof Cat.parent); // 'function'
console.log(Cat.parent == Animal); // true

The parent property of a class is a reference to its superclass. In our example, Animal.parent is undefined, since it doesn't have a superclass, but Cat.parent correctly points to Animal, since Cat is a subclass of Animal. While the parent property of classes doesn't have much use in development, there's another parent that is important. Before we talk about it, though, let's tackle a problem that arises with inheritance: how to access overridden methods.

When we look back at how the prototype chain is traversed, we remember that each object in the chain is checked for the property being accessed. If a property isn't found in the first item in the chain, the next item is checked—and so on until we find the property.

var Animal = function(){};

Animal.prototype.log = function(){
    console.log('This is an Animal.'),
};

Animal.prototype.eat = function(){
    console.log('The animal is eating.'),
};

var Cat = function(){};

Cat.prototype = new Animal();

Cat.prototype.eat = function(){
    console.log('The cat is eating.'),
};

var tibbs = new Cat();
tibbs.log(); // 'This is an Animal.'
tibbs.eat(); // 'The cat is eating.'

In this example, tibbs.log() refers to Animal.prototype.log. Because we didn't declare Cat.prototype.log, the original log method from our Animal.prototype is the one inherited by our Cat instances. However, we specified our own Cat.prototype.eat method, and because of the way the prototype chain is traversed, our Cat instances inherit this method instead of Animal.prototype.eat. We therefore say that Cat.prototype.eat overrides Animal.prototype.eat.

Overridden methods are very straightforward as long as you're not doing anything fancy. However, one problem that often comes up is how to access the overridden methods of a superclass. Because you won't be able to access the scope chain directly, you'll have to call the overridden method from the superclass directly via its prototype:

var Animal = function(){};

Animal.prototype.log = function(){
    console.log('This is an Animal.'),
};

Animal.prototype.eat = function(){
    console.log('The animal is eating.'),
};

var Cat = function(){};

Cat.prototype = new Animal();

Cat.prototype.eat = function(){
    Animal.prototype.eat.apply(this);
    console.log('The cat is eating.'),
};

var tibbs = new Cat();
tibbs.log(); // 'This is an Animal.'
tibbs.eat();
// 'The animal is eating.'
// 'The cat is eating.'

Here, we called the overridden eat method of Animal.prototype by directly invoking it using Animal.prototype.eat.apply(this). We need to use apply or call when accessing the overridden method because we want the function to be bound to the current instance.

MooTools classes use a more elegant approach. All subclasses are given a new method called parent, which can be used to call the overridden method:

var Animal = new Class({

    log: function(){
        console.log('This is an Animal.'),
    },

    eat: function(){
        console.log('The animal is eating.'),
    }

});

var Cat = new Class({

    Extends: Animal,

    eat: function(){
this.parent();
        console.log('The cat is eating.'),
    }

});

var tibbs = new Cat();
tibbs.log(); // 'This is an Animal.'
tibbs.eat();
// 'The animal is eating.'
// 'The cat is eating.'

If you look at our eat method in the Cat class, we called the parent method to call the overridden eat method of the Animal superclass. Like our original prototypal example, the call to this.parent() inside the eat method calls Animal.prototype.eat. Unlike our prototypal example, though, we no longer need to use apply or call, since the parent method already does this for us.

The parent method becomes really handy when used in conjunction with overridden methods that do lots of processing, like initializers:

var Animal = new Class({

    name: '',

    initialize: function(name){
        this.name = name;
    },

    getName: function(){
        return this.name;
    }

});

var Cat = new Class({

    Extends: Animal,

    initialize: function(name){
        this.parent(name);
        this.name += ', the Cat.';
    }

});

var tibbs = new Cat('Tobias'),
console.log(tibbs.getName()); // 'Tobias, the Cat.'

In this example, we first call the initialize method of the Animal class using this.parent, passing the necessary argument, before doing our own processing, which involves adding the string ', the Cat.' to the name property. In essence, the parent method not only makes it possible to call the overridden method, but also provides a way to "extend" it with our own property.

The parent method is available only to subclasses that inherit through the Extends property. If we tried to call this.parent() inside the Animal class, for instance, we'd get an error about not having a parent method.

Inside this.parent()

The parent method itself is an interesting function, and it's one of the most ingenious parts of the MooTools codebase. If you look at the source, you'll see that the parent method looks like this:

function(){
    if (!this.$caller) throw new Error('The method "parent" cannot be called.'),
    var name = this.$caller.$name, parent = this.$caller.$owner.parent;
    var previous = (parent) ? parent.prototype[name] : null;
    if (!previous) throw new Error('The method "' + name + '" has no parent.'),
    return previous.apply(this, arguments);
}

There's only one parent method in a subclass, but it changes depending on where it was called. We don't really need to learn how it works, but it's an interesting topic that will help us understand exactly how MooTools solves the problem of overridden methods. But before we can fully understand how the parent method works, we have to retrace a few steps.

Remember that before adding methods to the prototype, implement decorates the functions using the wrapper function we saw earlier. The wrapper function has several special properties, the most important ones being $owner and $name:

var Animal = new Class({

    name: '',

    initialize: function(name){
        this.name = name;
    },

    getName: function(){
        return this.name;
    }

});

console.log(Animal.prototype.getName.$owner == Animal); // true
console.log(Animal.prototype.getName.$name); // 'getName'

The $owner property points to the class itself, while $name is the name of the method you declared. In our example, Animal.prototype.getName.$owner points to the Animal class, while Animal.prototype.getName.$name has the value 'getName'. Take note that the $name property isn't taken from the identifier of the function itself, but from the key we used when we declared the method.

The main purpose of the wrapper function is to keep track of which method is currently called. When we call a method from an instance, the wrapped method sets the internal $caller property of the instance to itself. The $caller property is used to determine which current function is running. When we call tibbs.getName() for example, the $caller property will be set to the getName method.

Now when we call this.parent() inside a method, the function checks the $caller property to determine which of the methods invoked it. If there's no $caller property, it will throw an error, preventing parent from being called from outside a method. If there's a $caller, though, the parent method will store its $name property to determine the name of the method and then access the superclass using $owner.parent. The method then checks whether there's an overridden method from the superclass and invokes it, returning the arguments.

Let's trace how it all works with this example:

var Animal = new Class({

    name: '',

    initialize: function(name){
        this.name = name;
    },

    getName: function(){
        return this.name;
    }

});

var Cat = new Class({

    Extends: Animal,

    initialize: function(name){
        this.parent(name);
        this.name += ', the Cat.';
    }

});

var tibbs = new Cat('Tobias'),
console.log(tibbs.getName()); // 'Tobias, the Cat.'

First, we call new Cat(), thereby calling the initialize method. The $caller property is then set to initialize, with $caller.$owner pointing to the Cat class and $caller.$name set to 'initialize'. We then call this.parent() inside the initialize method, and it gets the superclass by accessing $caller.$owner.parent, which points to Animal. Finally, this.parent() checks for the existence of the initialize method in the Animal class and invokes it, using apply to rebind it to the current instance. The result is that we were able to invoke an overridden method from our superclass.

It might be hard to wrap you head around at first, but once you understand how parent works, you'll realize how elegantly MooTools solves complex problems to provide a nice API.

Mixins

JavaScript's inheritance model allows for only single direct inheritance. An object directly inherits only from a single prototype, although it can indirectly inherit from multiple prototypes via the prototype chain.

Using the prototype chain for multiple inheritance, though, has its limitations. Take, for example, the following snippet:

var Phone = new Class({

    sound: 'phone.ogg',

    initialize: function(number){
        this.number = number;
    },

    call: function(from){
this.ring();
        new Notification('New call!'),
    },

    ring: function(sound){
        sound = sound || this.sound;
        new Sound(sound).play();
    }

});

var AlarmClock = new Class({

    sound: 'alarm.ogg',

    initialize: function(alarmTime){
        this.time = alarmTime;
    },

    alarm: function(time){
        if (time == this.time) {
            this.ring();
            new Notification('Wake up sleepy head!'),
        }
    },

    ring: function(sound){
        sound = sound || this.sound;
        new Sound(sound).play();
    }

});

Here we have two classes, Phone and AlarmClock, and both have the same method called ring that plays a sound. This is considered bad design because we repeat the code for the ring method in both classes. When developing applications, we want to reuse code as much as possible.

We can, of course, simply make a new superclass that will be inherited by Phone and AlarmClock. But direct inheritance isn't a good design choice here: a phone and an alarm clock aren't really variations of the same object, so a RingingObject class is out of the question.

The MooTools class system supplies a very good system for code reuse:

var Ringer = new Class({

    sound: 'ring.ogg',

    ring: function(sound){
        sound = sound || this.sound;
        new Sound(sound).play();
    }

});

var Phone = new Class({
Implements: Ringer,

    initialize: function(number){
        this.number = number;
        this.sound = 'phone.ogg';
    },

    call: function(from){
        this.ring();
        new Notification('New call!'),
    }

});

var AlarmClock = new Class({

    Implements: Ringer,

    initialize: function(alarmTime){
        this.time = alarmTime;
        this.sound = 'alarm.ogg';
    },

    alarm: function(time){
        if (time == this.time) {
            this.ring();
            new Notification('Wake up sleepy head!'),
        }
    }

});

Here, we declare a new class called Ringer, and we transfer the sound and ring members to this new class. But instead of declaring them to be subclasses of this new class, we use the Implements keyword to declare that the Phone and AlarmClock classes should take the properties and methods of the Ringer class and use them.

The Ringer class is an example of a mixin. As its name implies, a mixin is a class that's "mixed in" or combined with another class. Mixins present a form of multiple inheritance, and the MooTools class system makes it easy to create and implement your own mixin.

The Implements keyword is used to add mixins to your class. It can either take a single value, which is a name of the class you'd like to combine, or an array of class names:

var Ringer = new Class({

    sound: 'ring.ogg',

    ring: function(sound){
        sound = sound || this.sound;
        new Sound(sound).play();
    }

});

var Charger = new Class({
charge: function(){
        new ElectricalSocket().connect(this);
    }

});

var Phone = new Class({

    Implements: [Ringer, Charger],

    initialize: function(number){
        this.number = number;
        this.sound = 'phone.ogg';
    },

    call: function(from){
        this.ring();
        new Notification('New call!'),
    }

});

var phone = new Phone(0000);
console.log(typeof phone.ring); // 'function'
console.log(typeof phone.charge); // 'function'

Mixins are actual classes, but they're usually written without an initialize method since they're not meant to be used like regular classes. Rather, mixins are considered to be collections of properties and methods that will be added to other classes, and they provide a simple way to share and reuse code among many classes.

The Implements keyword should give you a clue as to how mixins are added to classes. When the implement method sees this keyword, it creates new instances of these classes that are then fed to another implement call. This process adds the members of the instance that are inherited from the mixin class to the class itself. Without using the Implements keyword, we could mimic this process by using implement directly:

var Ringer = new Class({

    sound: 'ring.ogg',

    ring: function(sound){
        sound = sound || this.sound;
        new Sound(sound).play();
    }

});

var Phone = new Class({

    initialize: function(number){
        this.number = number;
        this.sound = 'phone.ogg';
    },
call: function(from){
        this.ring();
        new Notification('New call!' + this.from);
    }

});

Phone.implement(new Ringer);

This snippet shows exactly how mixins were done in older versions of MooTools. You shouldn't do this with the current versions, though, since it's verbose and hard to read. You should declare your mixins using Implements in your class declaration for readability.

One cool thing about mixins, though, is that the parent method respects their previous prototype chain rather than their new one. What this means is that when the parent method is called from within a method in a mixin that has been added to a class, the method will call the corrresponding overriden method in the mixin's original prototype chain:

var MixinSuper = new Class({

    log: function(){
        console.log('MixinSuper'),
    }

});

var Mixin = new Class({

    Extends: MixinSuper

    log: function(){
        this.parent();
    }

});

var Super = new Class({

    log: function(){
        console.log('Super'),
    }

});

var Sub = new Class({

    Extends: Super,

    Implements: Mixin

});

new Sub().log(); // 'MixinSuper'

Unlike direct inheritance using Extends, mixins aren't linked to the prototype chain. Instead, their members are added directly to the prototype of your class. This means that changes to a mixin after it's been implemented into your class aren't inherited:

var Ringer = new Class({

    sound: 'ring.ogg',

    ring: function(sound){
        sound = sound || this.sound;
        new Sound(sound).play();
    }

});

var Phone = new Class({

    Implements: Ringer,

    initialize: function(number){
        this.number = number;
        this.sound = 'phone.ogg';
    },

    call: function(from){
        this.ring();
        new Notification('New call!'),
    }

});

var phone = new Phone(0000);
console.log(typeof phone.ring); // 'function'

Ringer.implement('ringLoudly', function(sound){
    sound = sound || this.sound;
    new Sound(sound).playLoudly()
});

console.log(typeof phone.ringLoudly); // 'undefined'

Moreover, you can't call overridden methods via the parent method. The only way to call overridden mixin methods is to call them directly from your mixin's prototype, as we did in an example earlier.

The Wrap Up

In this chapter, we learned a lot about classical programming and how it differs from the prototypal model. We learned about classes, their similarities to prototypes, and the differences between the two. We also learned about the MooTools class system and how it provides a classical interface to JavaScript's object implementation. We found out how MooTools separates constructors from initializers, and how it encapsulates working with prototypes via the implement method. We dived into the internals of inheritance with classes, the parent method, and how to do multiple inheritance with mixins.

If you think we're done talking about classes, though, you're in for a surprise. The MooTools class system is as elegant as it is complex, and we've only touched the base of it in this chapter. In the next chapter, we'll learn more about the secrets of MooTools classes, as well as how to do complex programming using them.

So if you're ready, strap on your backpack and tie your shoelaces—it's time to trek deeper into the MooTools class system.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset