JavaScript. Prototype Inheritance

Keywords: Javascript Attribute

Write in front

This article only deals with prototype-based inheritance. After ES6, the inheritance based on Class es is referred to the relevant literature.

Knowledge Reserve

Two ways of calling constructors (with completely different results)

  • Call by keyword new:
function Person(name) {
    this.name = name;
    this.age = 18;
}
var o = new Person('hx');
console.log(o.name, o.age);
// hx 18
console.log(window.name, window.age);
// '' undefined
  • Direct call:
function Person(name) {
    this.name = name;
    this.age = 18;
}
var o = Person('hx');
console.log(o);
// undefined
console.log(window.name, window.age);
// hx 18

Thus it can be seen:

  • The constructor is the same as the ordinary function. It can be called directly and has no return value. this points to Window.
  • With a new call, the return value is an object, and this points to that object.

What on earth did new do?

The new keyword does the following:

  • Create an empty object;
  • Link the object to another object (that is, set the constructor of the object);
  • Take the empty object created in the first step as the context of this (this points to the empty object);
  • Execute the constructor (add attributes to the object) and return the object
function Person(name) {
    this.name = name;
    this.age = 18;
}
var o = new Person('hx');

The four steps corresponding to the above code are:

  • var obj = {};
  • obj.__proto__ = Person.prototype;
  • Person.call(obj,'hx');
  • return obj;

Several Ways to Implement Inheritance in JavaScript

1. Prototype Chain Inheritance

function Parent(name) {
    this.name = name;
    this.age = 18;
    this.arr = ['hello','world']
}
Parent.prototype.sayAge = function() {
    console.log(this.age)
}

function Child(gender) {
    this.gender = gender;
}
Child.prototype = new Parent();

var child1 = new Child('male');
child1.arr.push('js')
console.log(child1.name); // undefined
console.log(child1.age); // 18
console.log(child1.arr); // ['hello','world','js']
console.log(child1.gender); // male
child1.sayAge(); // 18

var child2 = new Child('female');
console.log(child2.name); // undefined
console.log(child2.age); // 18
console.log(child2.arr); // ['hello','world','js']
console.log(child2.gender); // female
child2.sayAge(); // 18

Advantage:

  • Methods on Parent prototype objects can be inherited by Child

Disadvantages:

  • Reference type attributes of Parent are shared by all Child instances and interfere with each other.
  • Child can't pass on to Parent

2. Constructor Inheritance (Classical Inheritance)

function Parent(name) {
    this.name = name;
    this.age = 18;
    this.arr = ['hello','world'];
    this.sayName = function() {
        console.log(this.name)
    }
}
Parent.prototype.sayAge = function() {
    console.log(this.age)
}

function Child(name,gender) {
    Parent.call(this,name); // this is pointed by Window s to the object to be created
    this.gender = gender;
}

var child1 = new Child('lala','male');
child1.arr.push('js');
console.log(child1.name); // lala
console.log(child1.age); // 18
console.log(child1.arr); // ['hello','world','js']
console.log(child1.gender); // male
child1.sayName(); // 18
child1.sayAge(); // Uncaught TypeError: child1.sayAge is not a function

var child2 = new Child('fafa','female');
console.log(child2.name); // fafa
console.log(child2.age); // 18
console.log(child2.arr); // ['hello','world']
console.log(child2.gender); // female
child2.sayName(); // 18
child2.sayAge(); // Uncaught TypeError: child1.sayAge is not a function

Advantage:

  • Avoid sharing reference type attributes with all Child instances
  • Child can refer to Parent

Disadvantages:

  • Methods on Parent prototype objects cannot be inherited by Child
  • Every time you create a Child instance, you create a sayName method, which wastes memory resources.

3. Combinatorial Inheritance

function Parent(name,age) {
    this.name = name;
    this.age = age;
    this.arr = ['hello','world']
}
Parent.prototype.sayName = function() {
    console.log(this.name)
}

function Child(name,age,gender) {
    Parent.call(this,name,age);
    this.gender = gender
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constuctor = Child;
Child.prototype.sayAge = function() {
    console.log(this.age)
}

var child1 = new Child('lala',18,'male');
child1.arr.push('js');
child1.name; // 'lala'
child1.age; // 18
child1.arr; // ['hello','world','js']
child1.gender; // 'male'
child1.sayName(); // lala
child1.sayAge(); // 18

var child2 = new Child('fafa',28,'female');
child1.name; // 'fafa'
child1.age; // 28
child1.arr; // ['hello','world']
child1.gender; // 'female'
child1.sayName(); // fafa
child1.sayAge(); // 28

Composite inheritance is the best practice of JavaScript inheritance

  • Attribute Inheritance Using Constructor - Avoid Parent Reference Attributes Influenced by Multiple Child Instances, and Support Passing Parameters
  • Method Using prototype chain inheritance-supporting Child inheritance Parent prototype object method, avoiding duplicate copies of methods in multiple instances.

Supplement 1

There are two types of methods for combining Child.prototype = Object.create(Parent.prototype) in inheritance code:

Child.prototype = Parent.prototype or Child.prototype = new Parent().

  • Child.prototype = Parent.prototype: This is definitely not possible, adding methods to Child.prototype or affecting Parent;
  • Child.prototype = new Parent(): This approach has a disadvantage that two Parent constructors (one is new Parent() and the other is Parent.call(this,name)) are called when a new Child instance is called, which wastes efficiency, and if the Parent constructor has side effects, repeated calls may have adverse consequences.

For the second case, besides using Object.create(Parent.prototype) method, it can also be implemented with a bridge function. In fact, regardless of the method, its realization idea is to adjust the prototype chain:

By:
new Child() ----> Child.prototype ----> Object.prototype ----> null

The adjustment is as follows:
new Child() ----> Child.prototype ----> Parent.prototype ----> Object.prototype ----> null

function Parent(name) {
    this.name = name
}
Parent.prototype.sayName = function() {
    console.log(this.name)
}

function Child(name,age) {
    Parent.call(this,name);
    this.age = age;
}

function F() {
}

F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constuctor = Child;

Child.prototype.sayAge = function() {
    console.log(this.age)
}

Thus, through a bridge function F, the Parent constructor is invoked only once, thus avoiding creating unnecessary and redundant attributes on Parent.prototype.

// Encapsulate the above method
function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

function prototype(child, parent) {
    var prototype = object(parent.prototype);
    child.prototype = prototype;
    prototype.constructor = child;
}

// When we use:
prototype(Child, Parent);

Supplement 2

What is the best way to inherit?

In fact, whether it is improved combinatorial inheritance (using Object.create or Object. setPrototype Of) or so-called parasitic combinational inheritance (using bridging function F), is not the key to answer this question.

The best way of inheritance embodies a design concept:
Regardless of static or dynamic attributes, the criteria for dividing dimensions are whether they are shared or not.

  • For each subclass, but the instances of subclasses are independent of each other (non-shared): the ++ should be placed on the constructor of the parent class, and then initialized by calling the constructor of the parent class through the subclass;
  • For each subclass, and the subclass instance can share attributes (whether static attributes or dynamic attributes): should be +++ placed on the parent class prototype object, obtained through the prototype chain;
  • For each subclass, and the subclass instances are independent attributes (non-shared): it should be + + implemented on the construction method of the subclass;
  • The attributes that are unique to each subclass, but can be shared by instances of subclasses: should ++ be placed on the prototype object of the subclass, and obtained through the prototype chain;

From the text is not easy to understand, look at the code:

function Man(name,age) {
    // Each subclass has its own, but independent (non-shared)
    this.name = name;
    this.age = age;
}

Man.prototype.say = function() {
    // Each subclass has and shares dynamic attributes (sharing)
    console.log(`I am ${this.name} and ${this.age} years old.`)
}
// Static attributes (shared) that each subclass has and shares
Man.prototype.isMan = true;

function Swimmer(name,age,weight) {
    Man.call(this,name,age);
    // Swimmer subclasses are unique and each instance is independent (non-shared)
    this.weight = weight;
}

function BasketBaller(name,age,height) {
    Man.call(this,name,age);
    // BasketBaller subclasses are unique and each instance is independent (non-shared)
    this.height = height;
}

// Using ES6 to directly set up prototype relationships to build prototype chains
Object.setPrototypeOf(Swimmer.prototype, Man.prototype)
// Equivalent to Swimmer.prototype = Object.create(Man.prototype); Swimmer.prototype.constructor = Swimmer;
Object.setPrototypeOf(BasketBaller.prototype, Man.prototype)
// Equivalent to BasketBaller.prototype = Object.create(Man.prototype); BasketBaller.prototype.constructor = BasketBaller;

// Continue to extend subclass prototype objects
Swimmer.prototype.getWeight = function() {
    // Swimmer subclasses are unique, but share dynamic properties (sharing)
    console.log(this.weight);
}
// Swimmer subclasses are unique, but share static properties (shared)
Swimmer.prototype.isSwimmer = true;

var swimmer1 = new Swimmer('swimmer1',11,100);
var swimmer2 = new Swimmer('swimmer2',21,200);

swimmer1; // Swimmer {name: "swimmer1", age: 11, weight: 100}
swimmer1.isMan; // ture
swimmer1.say(); // I am swimmer1 and 11 years old.
swimmer1.isSwimmer; // ture
swimmer1.getWeight(); // 100

swimmer2; // Swimmer {name: "swimmer2", age: 21, weight: 200}
swimmer2.isMan; // ture
swimmer2.say(); // I am swimmer2 and 21 years old.
swimmer2.isSwimmer; // ture
swimmer2.getWeight(); // 200

// BasketBaller's Identity (Brief)

Posted by Pintonite on Fri, 02 Aug 2019 03:11:15 -0700