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:

  1. var homework = {
  2. topic: "JS"
  3. };

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:

  1. 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:

  1. var homework = {
  2. topic: "JS"
  3. };
  4. var otherHomework = Object.create(homework);
  5. 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:

Prototype chain with 3 objects

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:

  1. homework.topic;
  2. // "JS"
  3. otherHomework.topic;
  4. // "JS"
  5. otherHomework.topic = "Math";
  6. otherHomework.topic;
  7. // "Math"
  8. homework.topic;
  9. // "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:

3 objects linked, with shadowed 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:

  1. var homework = {
  2. study() {
  3. console.log(`Please study ${ this.topic }`);
  4. }
  5. };
  6. var jsHomework = Object.create(homework);
  7. jsHomework.topic = "JS";
  8. jsHomework.study();
  9. // Please study JS
  10. var mathHomework = Object.create(homework);
  11. mathHomework.topic = "Math";
  12. mathHomework.study();
  13. // 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).

4 objects prototype linked

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!