JS inherits this



ECMAScript implements inheritance in more than one way. This is because the inheritance mechanism in JavaScript is not explicitly prescribed, but implemented through imitation. This means that all inheritance details are not entirely handled by interpreters. Suitable ways of inheritance can be determined according to the needs.

Original link

object masquerading

The constructor uses this keyword to assign values to all attributes and methods (i.e., constructors with class declarations). Because the constructor is only a function, you can make the ClassA constructor a method of ClassB, and then call it. ClassB receives the attributes and methods defined in the constructor of ClassA.

function ClassA(name) {
    this.name = name;
    this.sayName = function () {
        console.log(this.name);
    };
}

function ClassB(name,age) {
    this.classA = ClassA;
    this.classA(name);
    delete this.classA;
    this.age = age;
    this.sayAge = function(){
        console.log(this.age);
    }
}

var tom = new ClassA('Tom');
var jerry = new ClassB('Jerry',25);
tom.sayName();                         //'Tom'
jerry.sayName();                       //'Jerry'
jerry.sayAge();                        //25
console.log(tom instanceof ClassA);    //true
console.log(jerry instanceof ClassA);  //false
console.log(jerry instanceof ClassB);  //true

All new attributes and methods must be defined after the lines of code of the new method have been deleted, because the related attributes and methods of the superclass may be overridden.

Object impersonation implements multiple inheritance
If ClassA and ClassB exist, then ClassC wants to inherit these two classes, as follows:

function ClassA(name){
    this.name = name;
    this.sayName = function (){
        console.log(this.name);
    }
}

function ClassB(age){
    this.age = age;
    this.sayAge = function(){
        console.log(this.age);
    }
}

function ClassC(name,age){
    this.method = ClassA;
    this.method(name);
    
    this.method = ClassB;
    this.method(age);
    delete this.method;
}

var tom = new ClassC('Tom',25);
tom.sayName();                       //'Tom';
tom.sayAge();                        //25
console.log(tom instanceof ClassA);  //false
console.log(tom instanceof ClassB);  //false
console.log(tom instanceof ClassC);  //true

The disadvantage of this implementation is that if two classes ClassA and ClassB have properties or methods of the same name, ClassB has a high priority because it inherits from the subsequent classes.
Because of the popularity of this inheritance method, the third edition of ECMAScript adds two methods for Function objects, call() and apply().

call

The call method is the most similar to the classical object impersonation method. Its first parameter is used as the object of this, and other parameters are passed directly to the function itself.

function sayName(prefix) {
    console.log(prefix + this.name);
};

var tom = {};
tom.name = "Tom";

sayName.call(tom, 'This is ');  //'This is Tom'

The function sayName is defined outside the object, but you can also refer to this.
call method overrides object impersonation

function ClassA(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name);
    }
}

function ClassB(name,age){
    //this.method = ClassA;
    //this.method(name);
    //delete this.method;
    ClassA.call(this,name);
    this.age = age;
    this.sayAge = function (){
        console.log(this.age);
    }
}

var tom = new ClassB('Tom',25);
tom.sayName();                       //'Tom'
tom.sayAge();                        //25
console.log(tom instanceof ClassA);  //false
console.log(tom instanceof ClassB);  //true

The call method replaces the use of attributes to refer to ClassA.

apply

The apply method has two parameters, which are used as the object of this and an array of parameters to be passed to the function.

function sayName(prefex,mark) {
    console.log(prefex+ this.name+ mark);
};

var tom = {};
tom.name = 'Tom';

sayName.apply(tom, ['This is ','!']);  //'This is Tom!'

You can also use apply to overwrite objects to impersonate

function ClassA(name){
    this.name = name;
    this.sayName = function(){
        console.log(this.name);
    }
}

function ClassB(name,age){
    ClassA.apply(this,arguments);
    this.age = age;
    this.sayAge = function (){
        console.log(this.age);
    }
}

var tom = new ClassB('Tom',25);
tom.sayName();                       //'Tom'
tom.sayAge();                        //25  
console.log(tom instanceof ClassA);  //false
console.log(tom instanceof ClassB);  //true

Parametric arrays can be passed only if the order of parameters in the superclass is exactly the same as that in the subclass.

Prototype chain

Prototype object is a template. The object to be instantiated is based on this template. Any attributes and methods of prototype object are passed to all instances of this class. The prototype chain uses this function to implement inheritance mechanism.

function ClassA() {}
ClassA.prototype.name = 'Tom';
ClassA.prototype.sayName = function () {
    console.log(this.name);
};

function ClassB() {}
ClassB.prototype = new ClassA();
var tom = new ClassB();
tom.sayName();                       //'Tom'
console.log(tom instanceof ClassA);  //true
console.log(tom instanceof ClassB);  //true

Here, the prototype attribute of ClassB is set to an instance called ClassA to avoid assigning prototpye attributes one by one.
There are no parameters set when calling ClassA, because in the prototype chain, make sure that the constructor is parametric.
In the prototype chain, the result of instanceof also changed, returning true for both ClassA and ClassB.

Because of the reassignment of prototype attributes, new attributes in subclasses must appear after prototype is assigned.

function ClassA() {}
ClassA.prototype.name = 'Tom';
ClassA.prototype.sayName = function () {
    console.log(this.name);
};

function ClassB() {}
ClassB.prototype = new ClassA();
ClassB.prototype.age = 25;
ClassB.prototype.sayAge = function () {
    console.log(this.age);
};

var tom = new ClassA();
var jerry = new ClassB();
tom.sayName();                         //'Tom'
jerry.sayName();                       //'Tom'
jerry.name = 'Jerry';
tom.sayName();                         //'Tom'
jerry.sayName();                       //'Jerry'
jerry.sayAge();                        //25
console.log(tom instanceof ClassA);    //true
console.log(jerry instanceof ClassA);  //true
console.log(jerry instanceof ClassB);  //true

The disadvantage of the prototype chain is that multiple inheritance cannot be achieved because the prototype of the class will be rewritten.

Mixed mode

The problem with object impersonation is that constructors must be used, while constructors with parameters cannot be used with prototype chains, but you can try to combine the two.
Objects are used as attributes of inheritance constructors and prototype chains are used to inherit prototypes.

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

function ClassB(name, age) {
    ClassA.call(this, name);
    this.age = age;
}
ClassB.prototype = new ClassA();
ClassB.prototype.sayAge = function () {
    console.log(this.age);
};

var tom = new ClassA('Tom');
var jerry = new ClassB('Jerry',25);
console.log(tom instanceof ClassA);                    //true
console.log(jerry instanceof ClassA);                  //true
console.log(jerry instanceof ClassB);                  //true
console.log(jerry.constructor === ClassA);             //true
console.log(ClassB.prototype.constructor === ClassA);  //true

In the ClassB constructor, the name attribute of ClassA is inherited by using the object pretend, and the sayName method of ClassA is inherited by using the prototype chain inheritance. Because of using the prototype chain inheritance method, instanceof runs normally.
But the constructor attribute exposes the problem. Each prototype object has a constructor attribute pointing to its constructor, while the constructor of a ClassB instance pointing to ClassA, which can lead to the disorder of the inheritance chain. You can manually modify the direction of the constructor.

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

function ClassB(name, age) {
    ClassA.call(this, name);
    this.age = age;
}
ClassB.prototype = new ClassA();
ClassB.prototype.constructor = ClassB;
ClassB.prototype.sayAge = function () {
    console.log(this.age);
};

var tom = new ClassA('Tom');
var jerry = new ClassB('Jerry',25);
console.log(tom instanceof ClassA);                    //true
console.log(jerry instanceof ClassA);                  //true
console.log(jerry instanceof ClassB);                  //true
console.log(ClassA.constructor === ClassB);            //false
console.log(jerry.constructor === ClassA);             //false
console.log(ClassB.prototype.constructor === ClassA);  //false

Direct Inheritance Prototype Chain

In order to save memory, you can direct the prototype of ClassB to the prototype of ClassA without executing the creation of ClassA instances.

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

function ClassB(name, age) {
    ClassA.call(this, name);
    this.age = age;
}
ClassB.prototype = ClassA.prototype;
ClassB.prototype.constructor = ClassB;
ClassB.prototype.sayAge = function () {
    console.log(this.age);
};

var tom = new ClassA('Tom');
var jerry = new ClassB('Jerry',25);
console.log(ClassA.prototype.hasOwnProperty('sayAge'));  //true
console.log(ClassA.prototype.constructor === ClassB);   //true

This defect is due to the direct modification of the prototype chain pointing, and the attributes in the prototype chain of ClassB will also affect ClassA. Thus, ClassA has the sayAge method and the constructor attribute of ClassA is ClassB.

Empty objects as intermediaries

In order to solve the shortcomings of direct inheritance prototype chain, an empty object can be used as a mediator.

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

function ClassB(name, age) {
    ClassA.call(this, name);
    this.age = age;
}

var fn = function(){};
fn.prototype = ClassA.prototype;
ClassB.prototype = new fn();
ClassB.prototype.constructor = ClassB;
ClassB.prototype.sayAge = function () {
    console.log(this.age);
};
console.log(ClassA.prototype.hasOwnProperty('sayAge'));  //false
console.log(ClassA.prototype.constructor === ClassB);    //false   

Although object instances are created, modifying the prototype of ClassB will not affect ClassA because empty objects occupy almost no memory.

Encapsulated into extends method

function extends(child,parent){
    var fn = function (){};
    fn.prototype = parent.prototype;
    child.prototype = new fn();
    child.prototype.constructor = child;
    child.super = parent.prototype;
}

The flexibility of JS enables us to implement inheritance in a variety of ways. Understanding the principles and implementations of JS can help us choose suitable methods in different scenarios.

Keywords: Attribute ECMAScript Javascript

Added by hmiller73 on Sun, 19 May 2019 21:30:28 +0300