Chapter 8: Prototypes, inheritance

8.1 Prototypal inheritance

[[Prototype]]

Objects have a special hidden property [[Prototype]] (as named in the specification), that is either null or references another object (prototype).

When we want to read a property from object, and it’s missing, JavaScript automatically takes it from the prototype, which is called “prototypal inheritance”.

To set the internal property, use the special name __proto__. (getter/setter for [[Prototype]])

rabbit.__proto__ = animal;

The references can’t go in circles. Other types of __proto__ are ignored.

Writing doesn’t use prototype

The prototype is only used for reading properties. Write/delete operations work directly with the object. (Accessor properties are an exception.)

The value of “this”

No matter where the method is found: in an object or its prototype. In a method call, this is always the object before the dot. Methods are shared, but the object state is not.

for…in loop

The for..in loop iterates over inherited properties. It only lists enumerable properties.

obj.hasOwnProperty(key) returns true if obj has its own (not inherited) property named key. This method is stored in the Object.prototype object and all of its methods are not enumerable.

8.2 F.prototype

If F.prototype is an object, then the new operator uses it to set [[Prototype]] for the new object. Change to this property won't affect the existing objects created by the constructor.

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

Default F.prototype, constructor property

The default prototype is an object with the only property constructor that points back to the function itself. We can use constructor property to create a new object with the same constructor.

function Rabbit() {}

/* default prototype
Rabbit.prototype = { constructor: Rabbit };
*/

let rabbit = new Rabbit();
let anotherRabbit = new rabbit.constructor();

JavaScript itself does not ensure the right constructor value. To keep the right constructor, we can choose to add/remove properties to the default prototype instead of overwriting it as a whole.

8.3 Native prototypes

Object.prototype

let obj = {};
alert( obj ); // "[object Object]" ?

The string [object Object] is generated by the Object.prototype.toString() method. There is no more [[Prototype]] in the chain above Object.prototype.

Other built-in prototypes

Other built-in objects such as Array, Date, Function and others also keep methods in prototypes. Methods of thee built-ins also reside in the prototypes of their object wrappers.

Changing native prototypes

String.prototype.show = function() {
  alert(this);
};

"BOOM!".show(); // BOOM!

Prototypes are global, so it’s easy to get a conflict. There is only one case where modifying native prototypes is approved: polyfilling.

Borrowing from prototypes

let obj = {
  0: "Hello",
  1: "world!",
  length: 2,
};

obj.join = Array.prototype.join;
obj.join(','); // Hello,world!

The internal algorithm of the built-in join method only cares about the correct indexes and the length property. It doesn’t check if the object is indeed an array.

This can also be done by chaning the __proto__ property of the obj, but remember that we only can inherit from one object at a time.

8.4 Prototype methods, objects without proto

  • Object.create(proto, [descriptors]) – creates an empty object with given proto as [[Prototype]] and optional property descriptors.

  • Object.getPrototypeOf(obj) – returns the [[Prototype]] of obj.

  • Object.setPrototypeOf(obj, proto) – sets the [[Prototype]] of obj to proto.

These should be used instead of __proto__.

let animal = {
  eats: true
};

let rabbit = Object.create(animal, {
  jumps: {
    value: true
  }
});

The descriptors are in the same format as the Property flags and descriptors.

This call makes a truly exact copy of obj, including all properties: enumerable, non-enumerable, data properties, setters/getters, and the right [[Prototype]].

let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

"Very plain" objects

The __proto__ property is special: it must be either an object or null. When the object is used as associative arrays to store key/value pairs, it will cause error if the user assign an object to the __proto__ key.

__proto__ is a getter/setter inherited from its prototype, so we can create a plain object by setting its prototype to null:

let obj = Object.create(null);

Last updated