The specification document for ECMAScript defines the language as "an object-oriented programming language for performing computations and manipulating computational objects within a host environment." To put it simply, JavaScript is an object-oriented (OO) language.
The object-oriented approach focuses on objects, their composition, and how they interact with each other. While the whole area that is object-oriented programming is beyond the scope of this book, we'll discuss JavaScript's own flavor of object-oriented programming in this chapter.
Like the previous chapter on functions, there won't be much talk about MooTools here. The MooTools framework is an object-oriented framework at its core and we need to get a solid grasp of JavaScript's native object implementation to truly appreciate it. Don't worry, though, this is the last chapter where everything's vanilla JavaScript. We'll spend the rest of the book talking more about MooTools.
While all OO languages deal with objects at their core, the process by which objects are created and composed divides most OO languages into two camps:
Classical (or class-based) object-oriented languages use classes to create objects. A class is a special data type that serves as a blueprint for creating objects. In a classical OO language, you define the structure of an object by creating a class for it and then create the object itself by creating an instance of the class, in a process called instantiation.
Prototypal (or prototype-based) object-oriented languages, on the other hand, do not have classes but rely on other objects as blueprints. In a prototypal language, you create an actual object called the prototype that reflects the structure you want and this object is then used as a blueprint for your other objects. You create new instances by copying the prototype itself in a process called cloning. In pure prototypal languages, all objects can be used as prototypes.
JavaScript, at its core, is a prototypal language: there are no classes in JavaScript and objects are created from other objects. However, JavaScript is not a purely prototypal language. In fact, it has traces of classical features that we'll see later on in this chapter. If you're already familiar with other object-oriented languages, you'll probably find JavaScript to be weird, since the language has a unique—and somewhat quirky—object implementation.
However, don't be turned off too quickly: JavaScript—as an object-oriented language—is quite flexible and its fusion of classical and prototypal features give it a highly powerful base for complex and rich applications.
A JavaScript object is essentially an aggregate of key-value pairs. It's a complex data type, in contrast to "simple" data types like strings and numbers. Each key-value pair in an object is called a property, the key being the property name and the value being the property value.
The property name is always a string, but the property value can be any data type: primitives like strings, numbers, or Booleans, or complex data types like arrays, functions, and other objects. Although JavaScript makes no distinction about what data type a property contains, properties with functions as their values are often called methods to distinguish them from other properties. To avoid confusion, we'll adopt this practice in our discussion: properties with values other than functions will be called "properties" and properties with functions as their values will be called "methods". When we need to refer to both the properties and methods of an object, we'll use another term from object-oriented programming: members.
The lack of distinction between properties and methods in JavaScript arises from the fact that the language has first-class functions. From the perspective of the interpreter, any member of an object—regardless of value—is a property, since functions themselves can be used as values.
There are no limits to the number of properties an object can have, and an object can also have zero properties (making it an empty object). Depending on its use, an object can sometimes be called a hash or a dictionary or a table, reflecting its structure as a set of key-value pairs. However, we'll stick to object in our discussion.
The easiest way to create a new object in JavaScript is using the object literal:
// an object literal var person = { name: 'Mark', age: 23 };
Here, we create a new object with two properties, one with the key name
and the other with the key age
, and store it in our person
variable—giving us a person
object with two members. Notice that we didn't wrap the keys in quotes even though keys have to be strings. This is allowed in JavaScript as long as the keys are valid identifiers and not reserved words. Otherwise, we have to wrap our keys in quotes:
// object literal with different keys var person = { 'name of the person': 'Mark', 'age of the person': 23 };
To access a member of an object, we can use dot notation, which involves appending a period to the object's identifier and then the name of the key we want to access, or bracket notation, which involves appending a pair of square brackets, []
, to the end of the identifier containing a string value corresponding to the key:
var person = { name: 'Mark', age: 23 };
// dot notation console.log(person.name); // 'Mark' // bracket notation console.log(person['age']); // 23
The dot notation is actually just a shortcut—or syntactic sugar—for the bracket notation, although it's common practice to use dot notation most of the time. Of course, dot notation is limited to keys that are proper identifiers; for everything else, you'll have to use bracket notation.
var person = { 'name of the person': 'Mark', 'age of the person': 23 }; console.log(person['name of the person']); // 'Mark'
You'll also use bracket notation when you're not using a string literal for the key, but a variable that holds a string literal:
var person = { name: 'Mark', age: 23 }; // variable to hold the key var key = 'name'; console.log(person[key]); // 'Mark' Accessing an object's member that's not set will return the value undefined: var person = {}; console.log(person.name); // undefined
You can set the value of an object's member by explicitly defining it during the creation of the object like we did in the previous examples, but you can also set or modify a member's value by simply assigning a new value:
var person = {name: 'Mark'}; person.name = 'Joseph'; console.log(person.name); // 'Joseph' console.log(person.age); // undefined person.age = 23; console.log(person.age); // 23
You can create methods by simply assigning a function value to an object's member:
var person = { name: 'Mark', age: 23,
sayName: function(){ console.log(this.name); // 'Mark' } }; console.log(typeof person.sayName); // 'function' person.sayName(); person.sayAge = function(){ console.log(this.age); // 23 }; console.log(typeof person.sayAge); // 'function' person.sayAge();
You'll notice that we referred to the name
and age
members of our person
object using this.name
and this.age
in our methods. If you recall our discussion in the previous chapter, you know that the this
keyword for functions that are properties of objects refers to the object itself. In our case, the sayName
and sayAge
functions are methods of the person
object, and therefore the value of the this
keyword in their function bodies points to the person
object.
While object literals are an easy way to create new objects, they don't showcase JavaScript's full object-oriented capabilities. For one, limiting yourself to object literals would be time-consuming: if you need 30 objects to represent people with the properties name
and age
and logName
and logAge
methods, it would be impractical to create literals for each of these objects. In order to be efficient, we need a way to define the structure of our objects and use this definition to create new instances of the objects.
In a classical object-oriented language, we can create a new Person
class to define the structure of our objects. In a prototypal object-oriented language, we can simply create a base Person
object for the structure, then clone this object to create new instances. JavaScript—while nominally a prototypal language—takes an approach that fuses both the classical and prototypal way.
The first half of JavaScript's approach involves constructor functions (or simply, constructors). Object literals are actually syntactic sugar for creating objects without having to use a constructor. The following objects are equivalent:
// using an object literal.. var personA = { name: 'Mark', age: 23 }; // using a constructor.. var personB = new Object(); personB.name = 'Mark'; personB.age = 23;
The function Object
is our constructor function, and using var personB = new Object()
is the same as using var personB = {}
. By using new Object()
, we create a new blank object and this new object is said to be an instance of Object
.
The Object
constructor is special because it represents the "base" object in JavaScript: all objects in JavaScript, regardless of which constructor was used to create them, are instances of Object
. You can check whether an object is an instance of a constructor using the instanceof
operator:
// object literal var personA = {}; // constructor var personB = new Object(); // check whether objects are instances of Object console.log(personA instanceof Object); // true console.log(personB instanceof Object); // true
All objects also have a special property called constructor
, which is a reference to the constructor function that was used to create it. In the case of our simple objects above, the value of this property will be the Object
constructor:
// object literal var personA = {}; // constructor var personB = new Object(); // check whether objects used Object constructor console.log(personA.constructor == Object); // true console.log(personB.constructor == Object); // true
As the name implies, constructor functions are, obviously, functions. In fact, any JavaScript function (with the exception of host or interpreter-implemented functions) can be used as a constructor. This is one of the unique aspects of the language's object implementation: instead of creating a new construct for instantiating objects, JavaScript just relies on the readily available function construct.
Of course, you won't be using every function you create as a constructor. In almost all cases, you'll create your own functions that serve the sole purpose of being constructors for your classes. A constructor function is just like any other function—with slight changes in its internals—and it's common practice to define functions with their names capitalized to denote their nature as constructors:
// our Person constructor var Person = function(){}; // using the Person function as a regular function var result = Person(); console.log(result); // undefined // using the Person function as a constructor var person = new Person(); console.log(typeof person); // 'object' console.log(person instanceof Person); // true console.log(person.constructor == Person); // true
We created a new constructor called Person
by simply defining a blank function. When the Person
function is called like a normal function, it returns undefined
. However, when we use the new
keyword in conjunction with the Person()
invocation, something different happens: it returns a new object. It is this combination of the new
keyword and a call to a constructor function that makes object instantiation happen.
In our example, new Person()
returns a blank object, just like the object returned by new Object()
. The only difference in this case is that the object returned is no longer just an instance of Object
, it's also be an instance of Person
, and the constructor
property of the object now points to our new Person
constructor rather than Object
. But the object returned is still a blank object.
If you recall from the last chapter, the this
keyword inside functions refers to an object. In the case of our Person
function, it should refer to the global object when the function is used as a regular function (because it was declared in the global scope). However, something changes when the Person function is used as a constructor: this
no longer points to the global object, but instead points to the new object created:
// a global object var fruit = 'banana'; // our constructor var Person = function(){ console.log(this.fruit); }; // Person as a regular function Person(); // logs 'banana' // Person as a constructor new Person(); // logs undefined
We get undefined
for the last line because this.fruit
no longer points to any identifier available. It's the job of the new
keyword to create a new object and change the this
value of the constructor function to point to the new object.
At the start of this section, we ran into a problem with using simple object literals for object creation. We needed a way to create several copies of the person object without having to type each one of them via literals. Now that we know that constructor functions can be used to create objects and that the this
keyword refers to the new object, we can use this to solve our problem:
var Person = function(name, age){ this.name = name; this.age = age; }; var mark = new Person('Mark', 23); var joseph = new Person('Joseph', 22); var andrew = new Person('Andrew', 21); console.log(mark.name); // 'Mark' console.log(joseph.age); // 22 console.log(andrew.name + ', ' + andrew.age); // 'Andrew, 21'
The first thing you'll notice is that we changed our constructor a bit to accept arguments. This is allowed because constructors are just like regular functions, with the exception of having their this
keyword pointing to the newly created instance. When the call to new Person
is interpreted, a new object is created and the Person
function is invoked. Inside the constructor function, the arguments to name
and age
are set as the values of the object properties of the same name. The object is then returned.
Using constructors makes instantiating new objects of the same structure easier. Instead of explicitly defining the structure of each object via literals, you can simply create a constructor function that defines the structure ahead of time. This comes in handy when you need to add more members to your objects, especially when it comes to methods:
var Person = function(name, age){ this.name = name; this.age = age; this.log = function(){ console.log(this.name + ', ' + this.age); }; }; var mark = new Person('Mark', 23); var joseph = new Person('Joseph', 22); var andrew = new Person('Andrew', 21); mark.log(); // 'Mark, 23' joseph.log(); // 'Joseph, 22' andrew.log(); // 'Andrew, 21'
Here we added a new method called log
to our objects by simply declaring the value of this.log
as a logging function inside our constructor. If we were still using object literals, we would have to define the log function for each of our objects—and that would take too long. But by using a constructor function, we can define the function once for all our objects.
While constructor functions might seem like the end point for JavaScript's object implementation, they're only half of the picture. And if we limit ourselves to using constructor functions, we'll run into several problems.
One such problem is code organization. At the start of the last section, we wanted an easier way to create person objects with the properties name
and age
, and the methods setName, getName, setAge
, and getAge
. If we modify the Person
constructor from the previous section according to our specification, we end up with something like this:
var Person = function(name, age){ // properties this.name = name; this.age = age; // methods this.setName = function(name){ this.name = name; }; this.getName = function(){ return this.name; }; this.setAge = function(age){ this.age = age; };
this.getAge = function(){ return this.age; }; };
Our Person
constructor has suddenly ballooned—and we only have two properties and four methods! Imagine if you start creating complex objects with lots of members and complex methods. Your code would soon get hard to manage if you put everything inside your constructor.
Another issue that arises is extensibility. Suppose we have the following code:
// constructor.js var Person = function(name, age){ this.name = name; this.age = age; this.log = function(){ console.log(this.name + ', ' + this.age); }; }; // program.js var mark = new Person('Mark', 23); mark.log(); // 'Mark, 23'
In this example, the Person
constructor comes from another file we load from an external source into our program and we don't have access to change the code. But for our program, we need to add the new methods getName
and getAge
to our Person
instances. Since we can't modify the constructor itself, we can't add these new methods inside the constructor.
Then we get a brilliant idea: let's add the methods to the instances! Since new members can easily be added to methods by assignment, this would be easy to do. However, we quickly run into some problems:
// program.js var mark = new Person('Mark', 23); mark.log(); // 'Mark, 23' mark.getName = function(){ return this.name; }; mark.getAge = function(){ return this.age; }; mark.getName(); // returns 'Mark' mark.getAge(); // returns 23 var joseph = new Person('Joseph', 22); joseph.log(); // 'Joseph, 22' // the following lines will produce an error: joseph.getName(); joseph.getAge();
Even though we successfully managed to add new methods to our mark
instance, our joseph
instance didn't get the same methods. We end up with the same problem we had with object literals: we have to define the same set of members for each of our objects. This isn't very practical, even if we build a helper function to do it.
At the beginning of this chapter, we learned that JavaScript is a prototypal language and that the main feature of a prototypal language is its reliance on creating copies of an original object—the prototype—to define new objects instead of using classes. But looking back, we haven't seen any copying involved, nor did we see any original objects that serve as prototypes. All we saw were functions used as constructors and that new
keyword.
And that's our clue: the new
keyword. Remember that when we use new Object()
, the new
keyword creates a new object and uses that object as the this
value of our constructor function. Actually, the new
keyword isn't creating a new object: it's copying an object. And the object it's copying is none other than the prototype.
All functions that can be used as constructors have a special property called the prototype
, which is an object that defines the structure of your instances. When you use new Object()
, a new copy of Object.prototype
is made and this becomes your new object instance. This is another unique trait of JavaScript: unlike other prototypal languages where any object can be a prototype, JavaScript defines special prototype objects for the sole purpose of prototyping.
There is a way to mimic the prototypal style of other languages in JavaScript, though, wherein you directly clone any object to create a new object, instead of relying on prototypes. We'll learn how to do this in the last section of this chapter.
The prototype
object, like any other object, can have an unlimited number of members, and adding new members to it is simply a matter of assigning new values. We could rewrite our original Person
code in this way:
var Person = function(name, age){ this.name = name; this.age = age; }; Person.prototype.log = function(){ console.log(this.name + ', ' + this.age); }; var mark = new Person('Mark', 23); mark.log(); // 'Mark, 23'
Here, we moved the declaration of our log
method outside of the constructor. By assigning Person.prototype.log
, we tell the interpreter that all objects created from the Person
constructor should have a log
method, and this is reflected in our last line where we call mark.log()
. The rest of the constructor stays the same: we didn't move our this.name
and this.age
properties to the prototype
because we want to be able to set them when we invoke our constructor.
With prototypes in mind, we could also rewrite the code we had at the start of this section into something more manageable:
var Person = function(name, age){ this.name = name; this.age = age; }; Person.prototype.setName = function(name){
this.name = name; }; Person.prototype.getName = function(){ return this.name; }; Person.prototype.setAge = function(age){ this.age = age; }; Person.prototype.getAge = function(){ return this.age; };
This new code is much cleaner because we're not cramming everything inside our constructor and we can easily add more methods in the future without having to rearrange the constructor.
Another problem we had was how to add new methods when we can't change the constructor function. But since we already have access to the constructor function (and therefore its prototype), we can easily add new members without any access to the constructor itself:
// person.js var Person = function(name, age){ this.name = name; this.age = age; }; // program.js Person.prototype.log = function(){ console.log(this.name + ', ' + this.age); }; var mark = new Person('Mark', 23); mark.log(); // 'Mark, 23' var joseph = new Person('Joseph', 22); joseph.log(); // 'Joseph, 22'
We actually already saw a sample of dynamic prototype augmentation in the previous chapter on functions. One function form, the function object, used the Function
constructor and we were able to add new function methods through the MooTools function called Function.implement
. All JavaScript functions are actually instances of Function
, and Function.implement
actually modifies Function.prototype
. Even though we didn't have access to the Function
constructor itself—which is a built-in constructor that's provided by the interpreter—we're still able to add new function methods by augmenting Function.prototype
. The augmentation of native types will be discussed later on in the chapter on Types and Natives.
To understand prototypal programming in JavaScript, we need to be able to distinguish between prototypes and instances. As we learned earlier, a prototype is an object we use like a blueprint to define the structure of objects we want. When we copy the prototype, we create an instance of the prototype:
// Animal constructor var Animal = function(name){ this.name = name; }; // Animal prototype Animal.prototype.walk = function(){ console.log(this.name + ' is walking.'), }; // Animal instance var cat = new Animal('Cat'), cat.walk(); // 'Cat is walking.'
In this example, Animal
and its prototype
define the structure of our Animal
objects, and our object cat
is an instance of Animal
. When we execute new Animal()
, a copy of Animal.prototype
is created and we call this copy as an instance. Animal.prototype
is an object with a single member, walk
, which is a method and therefore all instances of Animal
also have the same walk
method.
But what happens if we change Animal.prototype
after we created an instance?
// Animal constructor var Animal = function(name){ this.name = name; }; // Animal prototype Animal.prototype.walk = function(){ console.log(this.name + ' is walking.'), }; // Animal instance var cat = new Animal('Cat'), cat.walk(); // 'Cat is walking.' // Does an Animal have an eat method? console.log(typeof cat.eat); // undefined, so no. // Add an eat method to Animal Animal.prototype.eat = function(){ console.log(this.name + ' is eating.'), }; console.log(typeof cat.eat); // 'function' cat.eat(); // 'Cat is eating'
Something interesting happened here. When we first checked the value of cat.eat
after we created the cat
object, we found that cat.eat
was undefined. We then added a new method to Animal.prototype
called eat
and checked again: cat.eat
is no longer undefined but is now a function. In fact, it's the same function we defined for Animal.prototype
.
It seems that defining new properties for a prototype updates all instances of the prototype, regardless of when they were created. Remember that when we create a new object, the new
operator creates a new copy of the prototype and when we created cat
, the prototype only had one method. If it's a real copy, it shouldn't have the eat method after we defined it in the prototype. After all, if you take a document and copy it on a photocopy machine and then write something on the original using a pen, you wouldn't expect that new change to automatically appear in the photocopied version, right?
Perhaps the interpreter knows when new properties are added to the prototype and automatically adds them to the instances? Maybe after we add the new eat
method to Animal.prototype
, it searches for all instances of Animal
and then adds this new method to them? We can easily check if this is the case by doing a simple experiment. After creating a cat
instance, we'll give it its own eat
method, and then we'll update the prototype. If the interpreter indeed copies methods from the prototype, the eat
method of our cat
instance should be overwritten:
// Animal constructor var Animal = function(name){ this.name = name; }; // Animal prototype Animal.prototype.walk = function(){ console.log(this.name + ' is walking.'), }; // Animal instance var cat = new Animal('Cat'), cat.walk(); // 'Cat is walking.' // Add a new eat method to cat cat.eat = function(){ console.log('Meow. Cat is eating!'), }; // Add an eat method to Animal Animal.prototype.eat = function(){ console.log(this.name + 'is eating.'), }; cat.eat(); // 'Meow. Cat is eating!'
Clearly, that's not the case. The JavaScript interpreter does not update the instances, because our cat.eat
method points to the one we defined for the cat object rather than the one from Animal.prototype
. So what's really happening?
All objects have an internal property called proto
that points to the object's prototype. This property is used by the interpreter to "link" the object to its prototype. While it's true that the new
keyword creates a copy of the prototype, it actually just creates a "superficial" copy, in the sense that the object it creates looks like the prototype. But the truth is that the object created by new is nothing more than a blank object that has its internal proto
property set to the prototype of the constructor.
You're probably asking, "Wait, if it's a blank object, why does it have methods and properties like the prototype? Where do those come from?" This is where the proto
property comes in. Objects are linked to their prototype so that the methods and the properties of the prototype can be accessed from the object. In our example, our cat
object didn't really have its own walk
method—it was actually the walk
method of Animal.prototype
. When the interpreter comes across the identifier cat.walk()
, it checks whether the cat
object has its own walk
method. Since we didn't explicitly define a walk
method for our cat
object, it checks the proto
property of the object to find its prototype, then checks the prototype to see if it has a walk
method. And seeing that there is indeed such a method in the prototype, the interpreter uses this method for the instance.
This also explains the eat
method of our other example: because we explicitly defined an eat
method for our cat
object, the eat
method of our Animal.prototype
wasn't called. An object overrides the members of the prototype if it has a member of the same key, and the members of an object's prototype are available only when they aren't overridden by the object's own members.
An object's member that's from the prototype (as opposed to a member that was explicitly defined for it) is said to be inherited, and the process of obtaining members from an object's prototype is called inheritance. You can check if an object has its own member using the hasOwnProperty
method that's available to all objects:
var Animal = function(){}; Animal.prototype.walk = function(){}; var dog = new Animal(); var cat = new Animal(); cat.walk = function(){}; console.log(cat.hasOwnProperty('walk')); // true console.log(dog.hasOwnProperty('walk')); // false
Here we explicitly defined a walk
method for our cat
object, but we didn't do the same for our dog
object. When we call cat.hasOwnProperty('walk')
, it returns true
because cat
now has its own walk
member. In contrast, dog.hasOwnProperty('walk')
returns false
because there's no explicitly defined walk
member for dog. Interestingly, if we do cat.hasOwnProperty('hasOwnProperty')
, it'll return false
, because the hasOwnProperty
method itself is inherited from Object
.
One thing to consider is the value of this
: inside a constructor, the value of this
always points to the instance and never to the prototype. Functions defined from the prototype have another rule: if they are called directly from the prototype, the this
value will point to the prototype
object, but when they are called from an object inheriting from the prototype, this will point to the instance rather than the prototype
:
var Animal = function(name){ this.name = name; }; Animal.prototype.name = 'Animal'; Animal.prototype.getName = function(){ return this.name; }; // Call `getName` directly from the prototype Animal.prototype.getName(); // returns 'Animal' var cat = new Animal('Cat'), cat.getName(); // returns 'Cat'
Here we changed our code a bit so that the Animal.prototype
object will have its own name
property. When we called the function directly, this.name
points to the value of the name
property of Animal.prototype
. However, when we called cat.getName(), this.name
points to the value of cat.name
(which we set in our constructor).
The prototype
and its instances are separate objects, and the link between them goes only one way: changes to the prototype
are reflected to all its instances but changes to the instances affect only the instances. However, weird behavior occurs when complex types come into play wherein changes to one instance affect all other instances.
Remember that in JavaScript we have both primitives and complex types. Primitives such as strings, numbers, and Booleans are always used by value: a copy is made when they are passed to functions or assigned to variables. However, complex types like arrays, functions, and objects are always used by reference, which means that they aren't copied but rather a "pointer" to the original complex type is created:
// Create an object var object = {name: 'Mark'}; // "Copy" the object into another variable var copy = object; console.log(object.name); // 'Mark' console.log(copy.name); // 'Mark' // Change the value of our copy copy.name = 'Joseph'; console.log(copy.name); // 'Joseph' console.log(object.name); // 'Joseph'
When we did var copy = object
, no new object was created. Rather, our variable copy
was assigned a reference to the same original object that was the value of object
. Therefore, both copy
and object
now reference the same object. A change made to copy
was therefore reflected to object
because they're really pointing to the same object.
Objects can have members that are complex types, such as an object with a property that's another object. Prototypes, being objects themselves, can also have complex types as properties. The problem arises when you define a property in a prototype that points to a complex object, because this property will then be inherited by all instances and they will all therefore point to a single object:
var Animal = function(){}; Animal.prototype.data = {name: 'animal', type: 'unknown'}; Animal.prototype.setData = function(name, type){ this.data.name = name; this.data.type = type; }; Animal.prototype.getData = function(){ console.log(this.data.name + ': ' + this.data.type); }; var cat = new Animal(); cat.setData('Cat', 'Mammal'), cat.getData(); // 'Cat: Mammal' var shark = new Animal(); shark.setData('Shark', 'Fish'), shark.getData(); // 'Shark: Fish' cat.getData(); // 'Shark: Fish'
Both the cat
and shark
objects don't have their own data
property, so they inherit the object from Animal.prototype
. Because of this inheritance, cat.data
and shark.data
both point to the same object that was defined from Animal.prototype
. Any change in one instance therefore gets reflected in other instances and gives us unwanted behavior.
The easiest way to solve this would be to remove the data property from Animal.prototype
and give our instances their own data
property. This could easily be done inside the constructor:
var Animal = function(){ this.data = {name: 'animal', type: 'unknown'}; }; Animal.prototype.setData = function(name, type){ this.data.name = name; this.data.type = type; }; Animal.prototype.getData = function(){ console.log(this.data.name + ': ' + this.data.type); }; var cat = new Animal(); cat.setData('Cat', 'Mammal'), cat.getData(); // 'Cat: Mammal' var shark = new Animal(); shark.setData('Shark', 'Fish'), shark.getData(); // 'Shark: Fish' cat.getData(); // 'Cat: Mammal'
Because the this
keyword inside our constructor points to the instance itself, setting this.data
inside it would give the instance its own data
property and won't affect our prototype. The end result is that all our instances now have their own data properties, and changing these properties will no longer affect other instances.
The Object
constructor and its corresponding prototype
is the base object in JavaScript. All objects, regardless of how they are constructed, inherit from Object. In the case of the following code, this is simple enough to understand:
var object = new Object(); console.log(object instanceof Object); // true
Because we constructed object
using the Object
constructor, we can say that the internal proto
property of object
points to Object.prototype
. However, consider the following code:
var Animal = function(){}; var cat = new Animal(); console.log(cat instanceof Animal); // true console.log(cat instanceof Object); // true console.log(typeof cat.hasOwnProperty); // 'function'
We know for a fact that cat
is indeed an instance of Animal
, since it was created using new Animal()
. We also know that all objects have a method called hasOwnProperty
, which they inherit from Object
. But how can our cat
object inherit from Object
when its internal proto
property points to Animal? And how can cat
be an instance of both Animal
and Object
at the same time when we didn't even use the Object
constructor?
The answer lies within the prototypes. By default, a constructor's prototype
is a basic object that has no methods or properties of its own. Sound familiar? Yes, it's like an object that's created using new Object()
! We could have written our code in the following form:
var Animal = function(){}; Animal.prototype = new Object(); var cat = new Animal(); console.log(cat instanceof Animal); // true console.log(cat instanceof Object); // true console.log(typeof cat.hasOwnProperty); // 'function'
And now it's clearer that Animal.prototype
inherits from Object
. Aside from inheriting from its own prototype
, an instance also inherits from the prototype
of the prototype
of the prototype
.
Sound confusing? Let's examine this in our code above. Our cat object is created using new Animal
, so it inherits properties and methods from Animal.prototype
. The value of Animal.prototype
is an object created using new Object()
, so it inherits the properties and methods from Object.prototype
. In turn, these properties and methods that our Animal.prototype
inherits from Object.prototype
are also passed to any instances of Animal
. And, therefore, we can say that our cat
object indirectly inherits from Object.prototype
.
The internal proto
property of our cat
object points to Animal.prototype
, and, in turn, the internal proto
property of our Animal.prototype
points to Object.prototype
. This continuous linking between the prototypes is called the prototype chain, and we can say that the cat
object's prototype chain extends from the cat
object itself to Object.prototype
.
Object.prototype
is always the end of the prototype chain, and this prototype's proto
property does not point to any other object—otherwise there would be no end to the prototype chain and it would be impossible to traverse it. Object.prototype
itself is not created using any constructors, but rather set by the interpreter internally, thus making it the only object that is not an instance of Object
.
The process of looking up properties and methods of an object through the prototype chain is called traversal. When the interpreter encounters cat.hasOwnProperty
, it first looks at the object itself to see if there's a member called hasOwnProperty
in the object. When it doesn't find one, it looks at the next object in the prototype chain, which is Animal.prototype
. As there's still no such member in this object, it moves on to the next object in the prototype chain, and so on. If it finds the member we're looking for in one of the objects in the chain, it stops the traversal and uses this member. If it reaches the end of the chain (which is Object.prototype
) and still does not find what it's looking for, it returns the value undefined
for the member. In our example, the traversal ends at Object.prototype
, where the hasOwnProperty
method comes from.
An object is always an instance of at least one constructor: for objects created using literals and objects created using new Object()
, they're instances of Object
. For objects created using a different constructor, they will be an instance of both the constructor used to create them as well as an instance of all the constructors used to create the prototypes in their prototype chain.
The prototype chain becomes useful once we start creating more complex objects. Say we want to have an Animal
object: all animals have a name, and all animals should be able to eat to survive. Therefore, we could write the following code:
var Animal = function(name){ this.name = name; }; Animal.prototype.eat = function(){ console.log('The ' + this.name + ' is eating.'), }; var cat = new Animal('cat'), cat.eat(); // 'The cat is eating.' var bird = new Animal('bird'), bird.eat(); // 'The bird is eating.'
So far so good. But now we want our animals to make noises, so we need to add methods. Moreover, these animals make different sounds: a cat meows and a bird tweets, so a cat needs a meow
method and a bird needs a tweet
method. Of course, we can simply create these methods on the animals themselves, but it would be impractical since we plan on creating many cat and bird objects. We could also simply add both these methods to Animal.prototype
, but that would be wasteful since birds never meow and cats never tweet.
What if we simply create separate constructors for both objects? We can create Cat
and Bird
constructors and modify their prototypes to fit the particular animal. But both these animals eat—do we really need to define an eat
method for each of the animals? That would mean quite a number of repeated declarations should we decide to add more types of animals.
With our knowledge of the prototype chain, we realize that there's a better solution. We can code our program so that we have separate Cat
and Bird
objects with proper methods and still inherit the methods that both animal share from our original Animal
prototype:
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 Bird = function(){}; Bird.prototype = new Animal('bird'),
Bird.prototype.tweet = function(){ console.log('Tweet!'), }; var cat = new Cat(); cat.eat(); // 'The cat is eating.' cat.meow(); // 'Meow!' var bird = new Bird(); bird.eat(); // 'The bird is eating.' bird.tweet(); // 'Tweet!'
We left our original Animal
constructor and prototype
as they were and created two new constructors, Cat
and Bird
. We used empty functions for our new constructors since we didn't have anything to set inside them, but we could have as easily added some other statements inside them if needed. The default prototypes for Cat
and Bird
were replaced with instances of Animal
, so our Cat
and Bird
objects will also inherit from Animal.prototype
. Finally, we added proper methods to the prototypes—meow
for Cat and tweet
for Bird. When we finally instantiated our objects, the results were two objects that inherit from both their immediate prototypes and from Animal.prototype
.
In classical programming languages, the process of creating a specialized version of a class by creating a new class and inheriting directly from the original class is called subclassing. JavaScript, being a prototypal language, does not have classes and, in essence, the only thing we are doing is creating a deliberate prototype chain. We say "deliberate" because we explicitly set which objects will be included in the prototype chain of our instances.
There is no limit to the size of your prototype chain, and you can extend your chain to allow for more specialization:
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 Persian = function(){ this.name = 'persian cat'; }; Persian.prototype = new Cat(); Persian.prototype.meow = function(){ console.log('Meow...'), }; Persian.prototype.setColor = function(color){
this.color = color; }; Persian.prototype.getColor = function(){ return this.color; }; var king = new Persian(); king.setColor('black'), king.getColor(); // 'black' king.eat(); // 'The persian cat is eating.' king.meow(); // 'Meow...' console.log(king instanceof Animal); // true console.log(king instanceof Cat); // true console.log(king instanceof Persian); // true
Here we created a new specialized version of Cat
called Persian
. You'll notice that we created a Persian.prototype.meow
method, which overwrites (during traversal) the original Cat.prototype.meow
method for instances of Persian
. If you check, you'll see that the king
object is an instance of Animal, Cat
, and Persian
, which means our prototype chain was correctly set.
The real power of prototype chains (deliberate or not) is when we see it in conjunction with inheritance and prototype chain traversal. Because all of the prototypes are linked, a change at one point in the chain will be reflected in the items below that point. If we add a new method or property to Animal.prototype
, for instance, all prototypes that inherit from Animal
will also receive those new members. This gives us a way to extend several objects easily and quickly.
As your programs grow in complexity, deliberate chains help keep your code organized. Instead of jamming all your code into one prototype, you can create multiple prototypes that have deliberate chains to reduce the amount of code you're working with and keep your program manageable.
You should realize by now that JavaScript's object-oriented flavor is in a class of its own. JavaScript's status as a prototypal language is largely nominal: constructor functions and the new
keyword are elements you'd expect to find in classical languages, and JavaScript's use of inheritance from prototypes—while clearly a prototypal characteristic—relies on specialized prototype
objects, making them similar to classes. The design of the language's object implementation was in part affected by language politics: JavaScript was created during a time when classical languages were the standard, and it was decided to give the language some features that would be familiar to classical programmers.
However, JavaScript is a flexible language. While we may not be able to change the core implementation of objects, we can leverage what's already available to give the language a more pure prototypal feel (and as we'll see in the next chapter, even a more classical feel).
In this "simplified" prototypal model, we'll forgo the complexities of JavaScript prototyping and focus on objects themselves. Instead of creating a constructor and setting prototypes, we'll use real objects as the prototypes and create new objects by "cloning" these prototypes. To get a better idea of what we're going to do, let's first use an example from another prototypal programming language called Io:
Animal := Object clone Animal name := "animal" Cat := Animal clone Cat name := "cat" myCat := Cat clone
Since this isn't a book about Io, we'll stick to the basics. Like JavaScript, the base object of Io is called Object
. However, Io's Object
isn't a constructor (i.e., not a function), but rather a real object. At the start of our code, we created a new object, Animal
, by cloning the original Object
object. Object clone
in Io means "access the clone method of Object and execute it," since Io uses spaces instead of periods or brackets to access properties. Next we set the name
property of Animal
to the proper string and then created a new object called Cat
by cloning the Animal
object and also set a name property for it. Finally, we created our myCat
object by cloning our final Cat
object.
We can do something similar in JavaScript:
var Animal = function(){}; Animal.prototype = new Object(); Animal.prototype.name = 'animal'; var Cat = function(){}; Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; var myCat = new Cat();
Similar, but not exactly the same. In our Io example, the final myCat
object inherits directly from Cat, Animal
, and Object
, which are all actual objects and not constructors. In our JavaScript example, our final myCat
object inherits from Cat, Animal
, and Object
via their prototype
properties, and Cat, Animal
, and Object
are functions rather than objects. In other words, Io does not have constructors and it clones directly from objects, while JavaScript has constructor functions and clones prototypes rather than real objects.
We could have the same feature in JavaScript if we could control the internal proto
property of objects. For instance, if we have an Animal
object and a Cat
object, we could change the proto
property of the Cat
object so that it links directly to the Animal
object (as opposed to linking to a prototype) so that Cat
would inherit directly from it.
While the proto
property itself is internal and can't be changed, several JavaScript engines have introduced a special object property called __proto__
that's accessible from the JavaScript API. The __proto__
property of an object can be used to change an object's internal proto
property, making it possible to directly inherit from another object:
var Animal = { name: 'animal', eat: function(){ console.log('The ' + this.name + ' is eating.'), } }; var Cat = {name: 'cat'}; Cat.__proto__ = Animal; var myCat = {}; myCat.__proto__ = Cat; myCat.eat(); // 'The cat is eating.'
There are no constructors in this example: Animal
and Cat
are real objects created using literals. By setting Cat.__proto__ = Animal
, we told the interpreter that the internal proto
property of Cat
should be the Animal
object. In the end, myCat
inherits from both Cat
and Animal
and its prototype chain does not have any objects that are prototype
. This is a simplified prototypal model that doesn't involve any constructors or prototype but instead relies on setting the prototype chain to use real objects.
You can take a similar approach via Object.create
, a new function introduced in ECMAScript 5. It takes a single argument, an object, and creates a new blank object with the internal proto
property set to the object passed to it:
var Animal = { name: 'animal', eat: function(){ console.log('The ' + this.name + ' is eating.'), } }; var Cat = Object.create(Animal); Cat.name = 'cat'; var myCat = Object.create(Cat); myCat.eat(); // 'The cat is eating.'
Notice that Object.create
is similar to the clone
method from Io, and internally, they actually do the same thing. When we did var Cat = Object.create(Animal)
, the interpreter created a new object and set its internal proto
property to point to the Animal
object. We can reproduce the original Io example using Object.create
and the results will be strikingly similar:
var Animal = Object.create({}); Animal.name = 'animal'; var Cat = Object.create(Animal); Cat.name = 'cat'; myCat = Object.create(Cat);
Unfortunately, while both these approaches are nice, they're not available everywhere. The __proto__
property is not a part of the ECMAScript specification, so not all JavaScript engines support it. Object.create
, while included in the specs, is from ECMAScript 5—which is less than a year old at the time of writing and isn't implemented in all engines yet. If you need multiple engine support for your programs (especially if it's a web application), keep in mind that these approaches won't work on all platforms.
There is, however, a way to implement Object.create
on older engines. Remember that JavaScript objects are used by reference. If you store an object in a variable x
and then do y = x
, both x
and y
will now point to the same object. Also, the prototype property of a function is an object and the default object can be overridden by simply assigning a new object to it:
var Animal = { name: 'animal', eat: function(){ console.log('The ' + this.name + ' is eating.'), } }; var AnimalProto = function(){}; AnimalProto.prototype = Animal; var cat = new AnimalProto();
console.log(typeof cat.purr); // 'undefined' Animal.purr = function(){}; console.log(typeof cat.purr); // 'function'
This should be familiar by now. We created an Animal
object with two members, a name
property and an eat
method. We then created an "intermediate" constructor called AnimalProto
and set its prototype
property to the Animal
object. Because of references, AnimalProto.prototype
and Animal
both point to the same object, and thus, when we created our cat
instance, it was actually inheriting directly from the Animal
object—just like an object produced by Object.create
.
With this in mind, we can mimic Object.create
for JavaScript engines that don't support it:
if (!Object.create) Object.create = function(proto){ var Intermediate = function(){}; Intermediate.prototype = proto; return new Intermediate(); }; var Animal = { name: 'animal', eat: function(){ console.log('The ' + this.name + ' is eating.'), } }; var cat = Object.create(Animal); console.log(typeof cat.purr); // 'undefined' Animal.purr = function(){}; console.log(typeof cat.purr); // 'function'
To start, we used if (!Object.create) ...
in the first line to check whether Object.create
already exists so that we won't overwrite it. Our Object.create
function is simple enough: it creates a new constructor called Intermediate
and sets this constructor's prototype
to the proto
object passed. It then returns an instance of this intermediate constructor. Because it uses the features already available in older implementations, our Object.create
function is compatible with almost every modern engine.
In this chapter we learned all about JavaScript's object implementation and how it differs from other languages. While at its core a prototypal language, JavaScript has features that put it in a different category as a blend of both classical and prototypal languages. We saw how we can create objects using simple literals and constructors with prototypes. We examined inheritance and how JavaScript traverses the prototype chain, and we implemented a simple prototypal model that hides the complexity of prototypes.
Because JavaScript is an object-oriented language at its core, the concepts we've learned here will help us in developing complex programs in JavaScript. And while the mechanics of object-oriented programming are beyond the scope of this book, I hope I've given you enough information to help you explore the topic further.
Now that we've learned how objects work with native JavaScript, we're left to ask a new question: "But what about MooTools?" This is a very interesting question because we didn't see much of MooTools in this chapter. Thankfully, we're just getting started and we still have room to learn one unique way MooTools handles objects. I call it unique because MooTools doesn't just use the concepts presented here to create simple features—it uses them to do something far more dramatic. And that's the subject of our next chapter.
So if you have your cup of tea or coffee ready, I suggest you sit back and relax as we explore MooTools in depth.