e9liig
Last Updated: September 23, 2016
·
127
· angelathewebdev

Back to basics: prototypal inheritance

JavaScript flavoured inheritance differs somewhat from the traditional OOP based programming. Even though now we have class keyword in ES6 to pretend that JavaScript can wear a disguise like OOP, it's still prototype-based inheritance under the hood.

Inherited Vs. Owned

Objects have properties (method is a special type of property whose value is a function), and these properties can be defined on the object itself or inherited from its prototype chain.

When a property is not found on the object itself, JavaScript engine inspects the prototype chain to find the said property. A property is considered not found if it's not available on any prototype (or link) on the prototype chain. So if a property is found in an earlier link of the chain, then it will shadow the same property found in the later link of the chain.

In a sense, JavaScript inheritance is finding shared properties available on a common breadcrumb.

There are a few ways of inheriting from an object in JavaScript:

  1. Using function to return you a base object. All properties you want to inherit is already on the base object. But instead of inheriting it, you're making a copy of it to use as part of this object.
  2. Using Object.create(baseObject) to set up baseObject as the first link on the prototype chain. Created object will have __proto__ property (depending on the JavaScript engine, this property may not be available to you, and ES6 also said this property should not be available to you)
  3. Using constructor and prototype chain, this is the most common or textbook style of inheritance you will see and it's also the main topic of this post.

Constructor and Prototype

If you don't know this yet, constructor manipulates this to create instance-owned properties and prototype manipulates __proto__ to create instance-shared properties. This is the biggest difference between them.

The created instance will have two special properties, and it's rather interesting why inheritance is indicated via not one, but two properties:

  • constructor, used to indicate which constructor it was created from
  • __proto__, first link of the prototype chain. Since it's a chain, in order to get to the second link, all you need to do is continue adding __proto__ property to the path until you reach null. In other words, if you are to write a lookup function, it would probably look like this:
function lookupPrototypeChain(obj, prop) {
  // this is not an object, so it's not a valid lookup
  if (obj !== Object(obj)) return undefined;

  while (obj.__proto__ !== null) {
    if (obj.hasOwnProperty(prop)) {
      return obj[prop];
    }

    // lookup the next item in the chain
    obj = obj.__proto__;
  }

  // nothing is found, return undefined explicitly
  return undefined;
}

__proto__ vs. prototype

Before jumping into an example, let's take a look at the difference between __proto__ and prototype.

__proto__ property is available on any JavaScript object to get its prototype chain while prototype is a property on the constructor function that is used to generate __proto__ object for all instances created by the constructor function.

Once that's cleared up, let's take a look at an example of inheritance using constructor and prototype:

function A() {}
A.prototype.isBored = function() {
  return Math.random() > 0.5;
};

function B() {}
// B gets A's prototype as its `__proto__`
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
B.prototype.haveFun = function() {
  console.log('Hooray!');
};

var b = new B();

// { constructor, __proto__, haveFun }, B's prototype
console.log(b.__proto__);

// constructor is function B, is the same as b.__proto__.constructor
// it's basically saying you don't have `constructor` property
// but your prototype chain does, so let's return that
b.constructor === B;

// makes sense, huh?
b.constructor.prototype === B.prototype; 

// constructor of any function is the top-level Function itself.
// You don't get A here because prototype chain doesn't live on `constructor` property, it lives on `__proto__` property
b.constructor.constructor === Function;

// { constructor, __proto__, isBored }, A's prototype
console.log(b.__proto__.__proto__);

// next link on the prototype chain is A
// again this is the same as b.__proto__.__proto__.constructor
b.__proto__.constructor === A;
b.__proto__.constructor.prototype = A.prototype;

// Again, this is top-level Function
b.__proto__.constructor.constructor === b.__proto__.constructor; 

Constructor and instantiation

Since constructor is just a function you can invoke it directly, it is possible to just call it to evaluate. However, the caveat is that it might involve calling this inside the constructor function. Under strict mode, JavaScript engine would result in an error when executing the function, so beware of its usage.

Other than invoking directly, the most common usage of constructor is to call it using the new operator. Here is what StackOverflow says about the process http://stackoverflow.com/questions/1646698/what-is-the-new-keyword-in-javascript

In short, the process goes like this: __proto__ object is generated from prototype first, bind this to current instance, execute constructor function, and return. This means a few things:

  1. Inside the constructor function, this can only refer to properties on the prototype object. You can't call owned properties outside of the constructor function because it's not declared yet.

  2. Even if you don't specify a prototype property, one is added automagically when you instantiate.

  3. this inside prototype methods and constructor function refer to the instance, not the prototype object or the constructor function. So if you want to refer to the prototype object itself inside a prototype method, you can either refer it as Constructor.prototype or this.constructor.prototype

Changing and Breaking things

If you modified prototype function after you instantiated, the two instances would share their .constructor.prototype property, but their .__proto__ is different. The reason is that on their prototype chain, they are pointing to the same object (.prototype) from which __proto__ is generated from while __proto__ is a snapshot of what prototype looks like at the time of instantiation.

Here is an example:

function A() {}
A.prototype.sayYo = function() {
  console.log('yo');
};

var a1 = new A();

A.prototype.sayHo = function() {
  console.log('ho');
};

var a2 = new A();

a1.constructor.prototype === a2.constructor.prototype;
a1.__proto__ === a2.__proto__;

If you modified constructor function after you instantiated, you need to ensure that prototype is set again. Otherwise, you're overwriting the function.

instanceof operator

In JavaScript, you can use instanceof operator to check if an object is an instance of a constructor.

A function object has an internal method called HasInstance that takes a value and compares its prototype with the function (or constructor)'s prototype. If you're to write instanceof yourself, you'd end up with something like this

function isInstanceOf(func, val) {
  if (val !== Object(val)) return false;

  while (val.constructor.prototype !== null) {
    if (val.constructor.prototype !== Object(val.constructor.prototype)) {
      throw new TypeError('input object does not have a prototype chain');
    }

    if (val.constructor.prototype === func.prototype) {
      return true;
    }

    val = val.__proto__;
  }

  return false;
}

Prototypal inheritance vs. OOP inheritance

Here is a quick summary of their differences

| Terms            | JavaScript | OOP |
|------------------|------------|-----|
| private variable | closure | `private` |
| private function | declared inside constructor function | `private` |
| public variable  | this.varName | `public` |
| public function  | this.methodName | `public` |
| static variable  | Constructor.varName | `static` |
| static function  | Constructor.prototype.methodName | `static` |

In the end

Depending on your usage of objects, here is a brief summary of things you can do with each way of inheriting an object

|                       | function | `Object.create` | constructor/prototype |
|-----------------------|----------|-----------------|-----------------------|
| has `constructor`     | false    | false           | true | 
| has `__proto__`       | false    | true            | true | 
| properties are shared | false    | true            | true | 
| properties are owned  | true     | false           | true | 
| customise instance    | true     | false           | true | 

Sources: