From: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes
JavaScript is often described as aprototype-based language— each object has aprototype object, which acts as a template object that it inherits methods and properties from. An object's prototype object may also have a prototype object, which it inherits methods and properties from, and so on. This is often referred to as aprototype chain, and explains why different objects have properties and methods defined on other objects available to them.
Well, to be exact, the properties and methods are defined on theprototype
property on the Objects' constructor functions, not the object instances themselves.
In classic OOP, classes are defined, then when object instances are created all the properties and methods defined on the class are copied over to the instance. In JavaScript, they are not copied over — instead, a link is made between the object instance and its prototype (its__proto__
property, which is derived from theprototype
property on the constructor), and the properties and methods are found by walking up the chain of prototypes.
Note:It's important to understand that there is a distinction between an object's prototype (which is available viaObject.getPrototypeOf(obj)
, or via the deprecated__proto__
property) and theprototype
property on constructor functions. The former is the property on each instance, and the latter is the property on the constructor. That is,Object.getPrototypeOf(new Foobar())
refers to the same object asFoobar.prototype
.
Let's look at an example to make this a bit clearer.
Let's go back to the example in which we finished writing ourPerson()
constructor — load the example in your browser. If you don't still have it from working through the last article, use ouroojs-class-further-exercises.htmlexample (see also the source code).
In this example, we have defined a constructor function, like so:
function Person(first, last, age, gender, interests) {
// property and method definitions
}
We have then created an object instance like this:
var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
If you type "person1.
" into your JavaScript console, you should see the browser try to auto-complete this with the member names available on this object:
In this list, you will see the members defined onperson1
's prototype object, which is thePerson()
constructor —name
,age
,gender
,interests
,bio
, andgreeting
. You will however also see some other members —watch
,valueOf
, etc — these are defined on thePerson()
constructor's prototype object, which isObject
. This demonstrates the prototype chain working.
So what happens if you call a method onperson1
, which is actually defined onObject
? For example:
person1.valueOf()
This method simply returns the value of the object it is called on — try it and see! In this case, what happens is:
person1
object has a valueOf()
method available on it.person1
object's prototype object(Person
) has avalueOf()
method available on it.Person()
constructor's prototype object(Object
) has avalueOf()
method available on it. It does, so it is called, and all is good!Note: We want to reiterate that the methods and properties arenotcopied from one object to another in the prototype chain — they are accessed by walking up the chain as described above.
Note: There isn't officially a way to access an object's prototype object directly — the "links" between the items in the chain are defined in an internal property, referred to as[[prototype]]
in the specification for the JavaScript language (seeECMAScript). Most modern browsers however do have a property available on them called__proto__
(that's 2 underscores either side), which contains the object's prototype object. For example, tryperson1.__proto__
andperson1.__proto__.__proto__
to see what the chain looks like in code!
So, where are the inherited properties and methods defined? If you look at theObject
reference page, you'll see listed in the left hand side a large number of properties and methods — many more than the number of inherited members we saw available on theperson1
object in the above screenshot. Some are inherited, and some aren't — why is this?
The answer is that the inherited ones are the ones defined on theprototype
property (you could call it a sub-namespace) — that is, the ones that begin withObject.prototype.
, and not the ones that begin with justObject.
Theprototype
property's value is an object, which is basically a bucket for storing properties and methods that we want to be inherited by objects further down the prototype chain.
SoObject.prototype.watch()
,Object.prototype.valueOf()
, etc., are available to any object types that inherit fromObject.prototype
, including new object instances created from the constructor.
Object.is()
,Object.keys()
, and other members not defined inside theprototype
bucket are not inherited by object instances or object types that inherit fromObject.prototype
. They are methods/properties available just on theObject()
constructor itself.
Note: This seems strange — how can you have a method defined on a constructor, which is itself a function? Well, a function is also a type of object — see theFunction()
constructor reference if you don't believe us.
Person.prototype
prototype
always starts empty. Now try the following:Object.prototype
You'll see a large number of methods defined onObject
'sprototype
property, which are then available on objects that inherit fromObject
, as shown earlier.
You'll see other examples of prototype chain inheritance all over JavaScript — try looking for the methods and properties defined on the prototype of theString
,Date
,Number
, andArray
global objects, for example. These all have a number of members defined on their prototype, which is why for example when you create a string, like this:
var myString = 'This is my string.';
myString
immediately has a number of useful methods available on it, likesplit()
,indexOf()
,replace()
, etc.
Important: Theprototype
property is one of the most confusingly-named parts of JavaScript — you might think that this points to the prototype object of the current object, but it doesn't (that's an internal object that can be accessed by__proto__
, remember?).prototype
instead is a property containing an object on which you define members that you want to be inherited.
Earlier on we showed how theObject.create()
method can be used to create a new object instance.
var person2 = Object.create(person1);
create()
actually does is to create a new object from a specified prototype object. Here,
person2
is being created using
person1
as a prototype object. You can check this by entering the following in the console:person2.__proto__
This will return theperson1
object.
Every constructor function has a prototype property whose value is an object containing aconstructor
property. This constructor property points to the original constructor function. As you will see in the next section that properties defined on the Person.prototype property (or in general on a constructor function's prototype property, which is an object, as mentioned in the above section) become available to all the instance objects created using the Person() constructor. Hence, the constructor property is also available to both person1 and person2 objects.
For example, try these commands in the console:
person1.constructor
person2.constructor
These should both return thePerson()
constructor, as it contains the original definition of these instances.
A clever trick is that you can put parentheses onto the end of theconstructor
property (containing any required parameters) to create another object instance from that constructor. The constructor is a function after all, so can be invoked using parentheses; you just need to include thenew
keyword to specify that you want to use the function as a constructor.
Try this in the console:
var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing']);
Now try accessing your new object's features, for example:
person3.name.first
person3.age
person3.bio()
This works well. You won't need to use it often, but it can be really useful when you want to create a new instance and don't have a reference to the original constructor easily available for some reason.
Theconstructor
property has other uses besides. For example, if you have an object instance and you want to return the name of the constructor it is an instance of, you can use the following:
instanceName.constructor.name
Try this, for example:
person1.constructor.name
Note: The value ofconstructor.name
can change (due to prototypical inheritance, binding, preprocessors, transpilers, etc.), so for more complex examples you'll want to use theinstanceof
operator instead.
Let's have a look at an example of modifying theprototype
property of a constructor function (methods added to the prototype are then available on all object instances created from the constructor).
prototype
property:Person.prototype.farewell = function() {
alert(this.name.first + ' has left the building. Bye for now!');
};
person1.farewell();
You should get an alert message displayed, featuring the person's name as defined inside the constructor. This is really useful, but what is even more useful is that the whole inheritance chain has updated dynamically, automatically making this new method available on all object instances derived from the constructor.
Think about this for a moment. In our code we define the constructor, then we create an instance object from the constructor,_then_we add a new method to the constructor's prototype:
function Person(first, last, age, gender, interests) {
// property and method definitions
}
var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);
Person.prototype.farewell = function() {
alert(this.name.first + ' has left the building. Bye for now!');
};
But thefarewell()
method is_still_available on theperson1
object instance — its available functionality has been automatically updated. This proves what we said earlier about the prototype chain, and the browser looking upwards in the chain to find methods that aren't defined on the object instance itself rather than those methods being copied to the instance. This provides a very powerful, extensible system of functionality.
Note: If you are having trouble getting this example to work, have a look at ouroojs-class-prototype.htmlexample (see itrunning livealso).
You will rarely see properties defined on theprototype
property, because they are not very flexible when defined like this. For example you could add a property like so:
Person.prototype.fullName = 'Bob Smith';
But this isn't very flexible, as the person might not be called that. It'd be much better to do this, to build thefullName
out ofname.first
andname.last
:
Person.prototype.fullName = this.name.first + ' ' + this.name.last;
This however doesn't work, asthis
will be referencing the global scope in this case, not the function scope. Calling this property would returnundefined undefined
. This worked fine on the method we defined earlier in the prototype because it is sitting inside a function scope, which will be transferred successfully to the object instance scope. So you might define constant properties on the prototype (i.e. ones that never need to change), but generally it works better to define properties inside the constructor.
In fact, a fairly common pattern for more object definitions is to define the properties inside the constructor, and the methods on the prototype. This makes the code easier to read, as the constructor only contains the property definitions, and the methods are split off into separate blocks. For example:
// Constructor with property definitions
function Test(a, b, c, d) {
// property definitions
};
// First method definition
Test.prototype.x = function() { ... }
// Second method definition
Test.prototype.y = function() { ... }
// etc.
This pattern can be seen in action in Piotr Zalewa's school plan app example.
This article has covered JavaScript object prototypes, including how prototype object chains allow objects to inherit features from one another, the prototype property and how it can be used to add methods to constructors, and other related topics.