prototype
is a property of every Function
object.
It gets created as soon as you define the function.
Its initial value is an empty object.
function foo(a, b) { return a * b; }
typeof foo.prototype // "object"
This empty object can be augmented with properties and methods.
They won't have any effect of the foo()
function itself.
They'll only be used when you use foo()
as a constructor.
Inside a function invoked with new
, the value this
contains the object to be returned by the constructor.
Augmenting (adding methods and properties to) this object is the way to add functionality to the object being created.
function Gadget(name, color) { this.name = name; this.color = color; this.whatAreYou = function() { return 'I am a ' + this.color + ' ' + this.name; } }
Adding methods and properties to the prototype
property of the constructor function is another way to add functionality to the objects this constructor produces.
Gadget.prototype.price = 100; Gadget.prototype.rating = 3; Gadget.prototype.getInfo = function() { return 'Rating: ' + this.rating + ', price: ' + this.price; };
Instead of adding to the prototype object, you can overwrite the prototype completely.
Gadget.prototype = { price: 100, rating: 3, getInfo: function() { return 'Rating: ' + this.rating + ', price: ' + this.price; } };
All the methods and properties added to the prototype are directly available as soon as a new object is created using the constructor.
var newtoy = new Gadget('webcam', 'black'); newtoy.name; // "webcam" newtoy.color; // "black" newtoy.whatAreYou(); // "I am a black webcam" newtoy.price; // 100 newtoy.rating; // 3 newtoy.getInfo(); // "Rating: 3, price: 100"
Objects are passed by reference in JavaScript, and therefore the prototype is not copied with every new object instance.
Prototype modifications, no matter when performed, imply that all objects (even those created before the modification) will inherit the changes.
Gadget.prototype.get = function(what) { return this[what]; }; newtoy.get('price'); // 100 newtoy.get('color'); // "black"
When trying to access a property of newtoy
, say newtoy.name
the JavaScript engine:
- will look through all of the properties of the object searching for one called name
- if it finds it, will return its value
newtoy.name // "webcam"
When trying to access a property belonging to newtoy
constructor's prototype (i.e. the prototype of the Gadget
function), rating
for example, the JavaScript engine:
- will examine all of the properties of newtoy and will not find the one called
rating
- then will identify the prototype of the constructor function used to create this object
- if the property is found in the prototype, this property is used
newtoy.rating // 3
Every object, in fact, has a constructor
property, which is a reference to the function that created the object.
newtoy.constructor // Gadget(name, color) newtoy.constructor.prototype.rating // 3
Every object has a constructor
.
The prototype of a constructor function is an object.
So it must have a constructor
too. Which in turn has a prototype.
The built-in Object
object is the highest-level parent, the end of the so called the prototype chain.
newtoy.toString() // "[object Object]"
newtoy
doesn't have an owntoString()
method and its prototype doesn't either.
In the end Object'stoString()
will use.
What if the object does have its own property and the prototype also has one with the same name?
The own property takes precedence over the prototype's.
function Gadget(name) { this.name = name; } Gadget.prototype.name = 'foo'; // "foo" var toy = new Gadget('camera');> toy.name; // "camera" delete toy.name; toy.name; // "foo" toy.name = 'camera'; toy.name; // "camera"
To list all properties of an object use a for-in
loop.
var o = {p1: 1, p2: 2}; for (var i in o) { console.log(i + '=' + o[i]); } // p1=1 // p2=2
There are some details to be aware of:
- Not all properties show up in a
for-in
loop. For example, the length (for arrays) and constructor properties will not show up. The properties that do show up are called enumerable. You can check which ones are enumerable with the help of thepropertyIsEnumerable()
method that every object provides. - Prototypes that come through the prototype chain will also show up, if properties provided are enumerable. You can check if a property is an own property versus prototype's using the
hasOwnProperty()
method. propertyIsEnumerable()
will return false for all of the prototype's properties, even those that are enumerable and will show up in the for-in loop.
function Gadget(name, color) { this.name = name; this.color = color; this.someMethod = function() { return 1; } } Gadget.prototype.price = 100; Gadget.prototype.rating = 3; var newtoy = new Gadget('webcam', 'black'); for (var prop in newtoy) { console.log(prop + ' = ' + newtoy[prop]); } // name = webcam // color = black // someMethod = function () { return 1; } price = 100 // price = 100 // rating = 3newtoy.hasOwnProperty('name') // true newtoy.hasOwnProperty('price') // falsefor (var prop in newtoy) { if (newtoy.hasOwnProperty(prop)) { console.log(prop + '=' + newtoy[prop]); } } // name=webcam // color=black // someMethod=function () { return 1; }newtoy.propertyIsEnumerable('name') // true newtoy.propertyIsEnumerable('constructor') // false newtoy.propertyIsEnumerable('price') // false newtoy.constructor.prototype.propertyIsEnumerable('price') // true
Every object gets the isPrototypeOf()
method.
This method tells whether that specific object is used as a prototype of another object.
var monkey = { hair: true, feeds: 'bananas', breathes: 'air' }; function Human(name) { this.name = name; } Human.prototype = monkey; var george = new Human('George'); monkey.isPrototypeOf(george) // true
It could be simply overwritten.
JavaScript engine access object's prototype through a secret link.
Some engines expose this link as a __proto__
properties.
Even when exposed never use secret
__proto__
property. It's no standard: think it's there only for learning purpose.
var monkey = { feeds: 'bananas', breathes: 'air' }; function Human() {} Human.prototype = monkey; var developer = new Human(); developer.feeds = 'pizza'; developer.hacks = 'JavaScript'; developer.hacks // "JavaScript", found in the object developer.feeds // "pizza", found in the object developer.breathes // "air", found in the prototypeIt is allowed to overwrite constructor property.
developer.constructor = 'junk' typeof developer.constructor.prototype // "undefifined", a string object haven't prototype property developer.breathes // "air", engine uses imutable __proto__ secret link
__proto__
is not the same as prototype
:
__proto__
is a property of the instancesprototype
is a property of the constructor functions.
typeof developer.__proto__ // "object" typeof developer.prototype // "undefifined"
The prototype chain is live with the exception of when you completely replace the prototype object.
function Dog(){this.tail = true;} var benji = new Dog(); var rusty = new Dog(); Dog.prototype.say = function() { return 'Woof!'; } benji.say(); // "Woof!" rusty.say(); // "Woof!" benji.constructor; // Dog() rusty.constructor; // Dog()
constructor of the prototype of a function is automatically set to the constructor function itself, rather than
Object()
benji.constructor.prototype.constructor // Dog()
Let's completely overwrite the prototype object with a brand new object
Dog.prototype = { paws: 4, hair: true };
Old objects do not get access to the new prototype's properties:
they still keep the secret link pointing to the old prototype object.
typeof benji.paws // "undefifined" benji.say() // "Woof!"
Any new objects created from now on will use the updated prototype.
var lucy = new Dog(); lucy.say() // TypeError: lucy.say is not a function lucy.paws // 4 lucy.constructor // Object() benji.constructor // Dog() typeof lucy.constructor.prototype.paws // "undefifined" typeof benji.constructor.prototype.paws // "number"
To fix the unexpected behavior, you have to reset the constructor property when overwrite the prototype.
Dog.prototype = {paws: 4, hair: true}; Dog.prototype.constructor = Dog;
When you overwrite the prototype, it is a good idea to reset the constructor property.