Prototypes
Where this
is a characteristic of function execution, a prototype is a characteristic of an object, and specifically resolution of a property access.
Think about a prototype as a linkage between two objects; the linkage is hidden behind the scenes, though there are ways to expose and observe it. This prototype linkage occurs when an object is created; it’s linked to another object that already exists.
A series of objects linked together via prototypes is called the “prototype chain.”
The purpose of this prototype linkage (i.e., from an object B to another object A) is so that accesses against B for properties/methods that B does not have, are delegated to A to handle. Delegation of property/method access allows two (or more!) objects to cooperate with each other to perform a task.
Consider defining an object as a normal literal:
var homework = {
topic: "JS"
};
The homework
object only has a single property on it: topic
. However, its default prototype linkage connects to the Object.prototype
object, which has common built-in methods on it like toString()
and valueOf()
, among others.
We can observe this prototype linkage delegation from homework
to Object.prototype
:
homework.toString(); // [object Object]
homework.toString()
works even though homework
doesn’t have a toString()
method defined; the delegation invokes Object.prototype.toString()
instead.
Object Linkage
To define an object prototype linkage, you can create the object using the Object.create(..)
utility:
var homework = {
topic: "JS"
};
var otherHomework = Object.create(homework);
otherHomework.topic; // "JS"
The first argument to Object.create(..)
specifies an object to link the newly created object to, and then returns the newly created (and linked!) object.
Figure 4 shows how the three objects (otherHomework
, homework
, and Object.prototype
) are linked in a prototype chain:
Fig. 4: Objects in a prototype chain
Delegation through the prototype chain only applies for accesses to lookup the value in a property. If you assign to a property of an object, that will apply directly to the object regardless of where that object is prototype linked to.
TIP: |
---|
Object.create(null) creates an object that is not prototype linked anywhere, so it’s purely just a standalone object; in some circumstances, that may be preferable. |
Consider:
homework.topic;
// "JS"
otherHomework.topic;
// "JS"
otherHomework.topic = "Math";
otherHomework.topic;
// "Math"
homework.topic;
// "JS" -- not "Math"
The assignment to topic
creates a property of that name directly on otherHomework
; there’s no effect on the topic
property on homework
. The next statement then accesses otherHomework.topic
, and we see the non-delegated answer from that new property: "Math"
.
Figure 5 shows the objects/properties after the assignment that creates the otherHomework.topic
property:
Fig. 5: Shadowed property ‘topic’
The topic
on otherHomework
is “shadowing” the property of the same name on the homework
object in the chain.
NOTE: |
---|
Another frankly more convoluted but perhaps still more common way of creating an object with a prototype linkage is using the “prototypal class” pattern, from before class (see Chapter 2, “Classes”) was added in ES6. We’ll cover this topic in more detail in Appendix A, “Prototypal ‘Classes’”. |
this
Revisited
We covered the this
keyword earlier, but its true importance shines when considering how it powers prototype-delegated function calls. Indeed, one of the main reasons this
supports dynamic context based on how the function is called is so that method calls on objects which delegate through the prototype chain still maintain the expected this
.
Consider:
var homework = {
study() {
console.log(`Please study ${ this.topic }`);
}
};
var jsHomework = Object.create(homework);
jsHomework.topic = "JS";
jsHomework.study();
// Please study JS
var mathHomework = Object.create(homework);
mathHomework.topic = "Math";
mathHomework.study();
// Please study Math
The two objects jsHomework
and mathHomework
each prototype link to the single homework
object, which has the study()
function. jsHomework
and mathHomework
are each given their own topic
property (see Figure 6).
Fig. 6: Two objects linked to a common parent
jsHomework.study()
delegates to homework.study()
, but its this
(this.topic
) for that execution resolves to jsHomework
because of how the function is called, so this.topic
is "JS"
. Similarly for mathHomework.study()
delegating to homework.study()
but still resolving this
to mathHomework
, and thus this.topic
as "Math"
.
The preceding code snippet would be far less useful if this
was resolved to homework
. Yet, in many other languages, it would seem this
would be homework
because the study()
method is indeed defined on homework
.
Unlike many other languages, JS’s this
being dynamic is a critical component of allowing prototype delegation, and indeed class
, to work as expected!