[JavaScript] Class Inheritance and Prototype Chain

Aaron Lu
5 min readMar 10, 2019

Constructor Function (Blueprint)

// declaration
function Person(name, age) {
this.name = name;
this.age = age;
};
// expression
var Person = function(name, age) {
this.name = name;
this.age = age;
};

Notice the use of this to assign values to the object's properties based on the values passed to the function.

ES6 Classes

// declaration
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// expression
var Person = class {
constructor(name, age) {
this.name = name;
this.age = age;
}
}

Nowadays, we can use class keyword to define a classes, because it is introduced in ES2015, it is just a syntactical sugar.

Why do we need Class?

DRY (Don’t Repeat Yourself)

More Structured way to organize the data.

Say you have 100 cars with 50 components each. Then you have to declare the same properties for each object every time you create the car object.

With class you only need to pass values with arguments, creating the object.

function Car(type, model, color) {
this.type = type;
this.model = model;
this.color = color;
};
var tesla = new Car("Tesla", "Model S", "white");

Class body and method definitions

Constructor

The constructor method is a special method for creating and initializing an object created with a class.
A constructor can use the super keyword to call the constructor of the super class.

Static methods

The static keyword defines a static method for a class. Static methods are called without instantiating their class and cannot be called through a class instance. Static methods are often used to create utility functions for an application.

class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
console.log(Point.distance(p1, p2)); // 7.0710678118654755

The method distance can only be access through Point.

Inheritance

When it comes to inheritance, JavaScript only has one construct: objects.

Inheritance is a major feature of object-oriented programming. Data abstraction can be carried up several levels, that is, classes can have superclasses and subclasses.

As an app developer, you can choose which of the superclass’s attributes and methods to keep and add your own, making class definition very flexible. Some languages let a class inherit from more than one parent (multiple inheritance).

Reference: https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Objects/Object_prototypes

每個物件都有一個連著其他原型(prototype)的私有屬性(private property)物件。幾乎所有 JavaScript 的物件,都是在原型鏈最頂端的 Object 實例。

Benefits

Reusability: The existing classes remain unchanged. By reusability, the development time of software is reduced.

When a class is derived from more than one class, all the derived classes have similar properties to those of base classes.

function Person(first, last, age, gender, interests) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
this.interests = interests;
};
Person.prototype.greeting = function() {
alert('Hi! I\'m ' + this.name.first + '.');
};
function Teacher(first, last, age, gender, interests, subject) {
Person.call(this, first, last, age, gender, interests);

this.subject = subject;
}
Teacher.prototype = Object.create(Person.prototype);
Teacher.prototype.constructor = Teacher;

Create a Subclass with extends

The extends keyword is used in class declarations or class expressions to create a class as a child of another class.

ES6 classes

class Animal { 
constructor(name) {
this.name = name;
}

speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
constructor(name) {
super(name); // call the super class constructor and pass in the name parameter
}
speak() {
console.log(this.name + ' barks.');
}
}

If there is a constructor present in the subclass, it needs to first call super() before using “this”.

Traditional function-based “classes”

function Animal (name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(this.name + ' makes a noise.');
}
class Dog extends Animal {
speak() {
console.log(this.name + ' barks.');
}
}

Prototype Chain

JavaScript 透過物件的 prototype 屬性實現繼承機制。物件的原型物件可能也具備原型物件,並繼承了其上的函式與屬性,這就是所謂的「原型鍊 (Prototype chain)」。

The properties and methods are defined on the prototype property on the Objects' constructor functions, not the object instances themselves.

In JavaScript, a link is made between the object instance and its prototype (its __proto__property, which is derived from the prototype property on the constructor), and the properties and methods are found by walking up the chain of prototypes.

Prototype Chain Workflow

function Person(name, gender) {
this.name = name;
this.gender = gender;
}
var person1 = new Person('Tom', 'Male');

When you call a method valueOf on person1, which is actually defined on Object. This method — Object.valueOf() is inherited by person1 because its constructor is Person(), and Person()'s prototype is Object().

person1.valueOf()
  • The browser initially checks to see if the person1 object has a valueOf() method available on it, as defined on its constructor, Person().
  • It doesn’t, so the browser then checks to see if the Person() constructor's prototype object (Object()) has a valueOf() method available on it. It does, so it is called, and all is good!

使用原型鍊繼承

繼承屬性

JavaScript 物件是一「包」動態的屬性(也就是它自己的屬性)並擁有一個原型物件的鏈結,當物件試圖存取一個物件的屬性時,其不僅會尋找該物件,也會尋找該物件的原型、原型的原型……直到找到相符合的屬性,或是到達原型鏈的尾端。

// 利用含有 name 與 age 屬性的 Person 函式,建立一個 ben 物件:
function Person(name, age) {
this.name = name;
this.age = age;
}
let ben = new Person('Ben', 20); // {name: 'Ben', age: 20}
// 接著針對 Person 函式的原型添加屬性(age = 30, gender = 'Male')
Person.prototype.age = 30;
Person.prototype.gender = 'Male';
// Person.[[Prototype]] 有 age 與 gender 的屬性:{age: 30, gender:' Male'}
// Person.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最後 Person.[[Prototype]].[[Prototype]].[[Prototype]] 成了 null
// 這是原型鏈的結末,因為 null 按照定義並沒有 [[Prototype]]。
// 因此,整個原型鏈看起來就像:
// {name: 'Ben', age: 20} ---> {age: 30, ge: 4} ---> null
console.log(ben.name); // 'Ben'
// ben 有屬性「name」嗎? > 有,該值為 Ben。
console.log(ben.age); // 20
// ben 有屬性「age」嗎? > 有,該數值為 20。
// ben 還有個原型屬性「age」,但這裡沒有被訪問到。
// 這稱作「property shadowing」。
console.log(ben.gender); // 'Male'
// ben 有屬性「gender」嗎?沒有,那就找 Person 的原型看看。
// ben 在「Person.[[Prototype]]」有屬性「gender」嗎?有,該值為 'Male'。

繼承方法

在 Javascript 裡,任何函式都能以屬性的方式加到物件中。一個被繼承的函式的行為就像是其他屬性一樣,其中也包含了上述的 property shadowing(在這種情況下叫做 method overriding)。

// 利用含有 name 與 age 屬性的 Person 函式,建立一個 ben 物件:
function Person(name, age) {
this.name = name;
this.sayHi = function() { console.log(`Hi ${this.name}`); }
}
let ben = new Person('Ben'); // {name: 'Ben'}
ben.sayHi(); // 'Hi! Ben'// Ben 改名為 Tom
ben.name = Tom;
ben.sayHi(); // 'Hi! Tom'

--

--

Aaron Lu

Software Engineer with experience in languages such as HTML5, CSS3, JavaScript, Node.js and MySQL, frameworks such as React, React-Native, express and Nest.js