JavaScript is full of objects. In the previous posting, you learned how to create objects using constructors and literals. You saw a couple notations for creating objects. You learned how to add properties and methods.
In this lesson, you will learn about prototypes. A prototype is an object from which other objects inherit properties.
Every object has a prototype by default. Since prototypes are themselves objects, every prototype has a prototype too. (There is only one exception, the default object prototype at the top of every prototype chain.)
Object Review
An object in JavaScript is any unordered collection of key-value pairs. If it’s not a primitive (undefined, null, boolean, number, or string) — it’s an object.
In our last post, you learned how to create an object with properties and methods, and then use the new keyword to creates instances of the object. Here’s an example:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Cat(name) { | |
this.name = name; | |
this.talk = function() { | |
console.log( this.name + " says meeow!" ); | |
} | |
} | |
var cat1 = new Cat("felix"); | |
cat1.talk(); //sends "felix says meeow!" to the console | |
var cat2 = new Cat("ginger"); | |
cat2.talk(); // sends "ginger says meeow!" to the console |
The function Cat() acts as an object constructor. Its properties and methods are declared inside it by prefixing them with the keyword this. Objects defined using an object constructor are then instantiated using the new keyword. Now you can easily define multiple instances of Cat, each with its own name.
Prototype Concepts
The name prototype comes from the idea that in JavaScript, an object is created as a copy of an existing example (that is, a prototype) object. Any properties and methods of this prototype object will appear as properties and methods of the objects created from that prototype’s constructor. You can say that these objects inherit their properties and methods from their prototype.
In JavaScript, every function has a property named prototype that refers to a prototype object. This prototype object in turn has a property named “constructor,” which refers back to the function itself. It’s sort of a circular reference.
Now, when a function (in the example above, Dog) is used to create an object with the “new” operator, the resulting object will inherit the properties of Dog.prototype. See that the Dog.prototype object has a constructor property that points back to the Dog function. Consequently, every Dog object (that inherits from Dog.prototype) will also appear to have a constructor property that points back to the Dog function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var spot = new Cat("Spot"); | |
// Cat.prototype is the prototype of spot | |
console.log(Cat.prototype.isPrototypeOf(spot)); | |
// spot inherits the constructor property from Cat.prototype | |
console.log(spot.constructor == Cat.prototype.constructor); | |
console.log(spot.constructor == Cat); | |
// But constructor property doesn’t belong to spot. The line below displays "false" | |
alert(spot.hasOwnProperty("constructor")); |
Creating Multiple Cat Instances Using Prototype
Suppose we want to add another function to Cat, after we have defined it. Prototype is a type of inheritance in JavaScript.
Use it when you want an object to inherit a method after it has been defined. You can think of prototyping as “attaching” a method to an object after it’s been defined. And all object instances will then instantly share this new behavior.
Our example continue from the previous code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Cat.prototype.changeName = function(name) { | |
this.name = name; | |
} | |
var firstCat = new Cat("cc"); | |
firstCat.changeName("bill"); | |
firstCat.talk() //will now send "bill says meeow!" to the console |
In JavaScript, every function has a property named prototype that refers to a prototype object.
You can get the prototype using getPrototypeOf() and you can determine if two objects have the same prototype:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Object.getPrototypeOf("felix") === Object.getPrototypeOf("ginger"); |
Chain of Prototypes
Every JavaScript object inherits a chain of prototypes, all of which terminate with Object.prototype.
It is done using a simple algorithm, as follows: when you try to access a property/method of an object, JavaScript checks if that property/method is defined in that object. If not, then the object’s prototype will be checked. If not, then that object’s prototype’s prototype will be checked, and so on, all the way to Object.prototype.
Instead of having a separate instance of a function object for every object, you can make the objects share the method by putting it inside the prototype.
Property Access Errors
It is not an error to query a property that does not exist. If a particular property is not found as its own property or an inherited property, the property access expression o.x evaluates to undefined.
For example, if I am to call see if I can get spot’s mother’s name, the value will be returned as undefined.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var motherName = spot.mother.name; |
This raises a TypeError exception. Unless you are certain the property exists, you should test to be sure properties exist.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Here’s a verbose technique: | |
var motherName = undefined; | |
if(spot) { | |
if (spot.mother) motherName = spot.mother.name; | |
} | |
// And here is a more concise way. | |
var motherName = spot && spot.mother && spot.mother.name; |
Object.create
Inheritance is a way to create a class as a specialized version of one or more classes (JavaScript only supports single class inheritance). The specialized class is commonly called the child, and the other class is commonly called the parent. In JavaScript you do this by:
Assigning an instance of the parent class to the child class, and then specializing it.
In modern browsers you can also use Object.create() to implement inheritance by passing in the prototype.
Here is an example of creating an object that has the same internal prototype as the Shape object.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Create the shape object. | |
var Shape = { twoDimensional: true, color: undefined, hasLineSegments: undefined }; | |
var Square = Object.create(Object.getPrototypeOf(Shape)); |
References
- Prototypes and Inheritance in JavaScript
- Understanding JavaScript Prototypes
- Create Advanced Web Applications With Object-Oriented Techniques
- ECMAScript Language Specification
- Introduction to Object-Oriented JavaScript on Mozilla Developer Network
- JavaScript and Object Oriented Programming (OOP) in JavaScriptKit