Deep Prototype Chain and Implementation of Inheritance in JavaScript Learning Series

Keywords: Attribute Javascript Java JQuery

JavaScript is an interpretive language. It does not need to be compiled into binary files. It is a dynamic language. Unlike the implementation of inheritance in Java, JavaScript implements inheritance mainly by relying on powerful prototype chain mechanism.

A prototype chain

When accessing an object's attributes, it first looks in the basic attributes. If not, JavaScript traverses the prototype chain upwards until it finds the attributes with a given name. When it finds the attributes that reach the top of the prototype chain (that is, Object.prototype), it returns undefined.

var obj = {};
obj.a //Output undefined
var another = { a : 2};
obj.__proto__ = another;
obj.a //Output 2

Object Attribute Shielding

When an object obj's attribute a is read, if the obj itself does not have an attribute named a, it goes to the prototype chain to find the attribute. But when an attribute a is written in obj, that is, when the assignment statement obj.a = 4 is executed, there are several cases as follows:

  • If an obj object contains an attribute named a, it masks an attribute named a on all prototype chains. This assignment statement only modifies existing properties.
var another = { a : 2};
var obj = {a : 4};
obj.__proto__ = another;
obj.a //Output 4
obj.a = 5;
another.a //Output 2
obj.a //Output 5
  • If an obj object does not contain an attribute named a, and its prototype chain contains an attribute named a and is not marked as read-only, a new attribute named a will be added directly to obj.
var another = { a : 2};
var obj = {};
obj.__proto__ = another;
obj.a //Output 2
obj.hasOwnProperty('a')//Output false
obj.a = 5;
another.a //Output 2
obj.a //Output 5
obj.hasOwnProperty('a')//Output true
  • If an obj object does not contain an attribute named a and its prototype chain contains an attribute named a and is marked as read-only, it is impossible to modify the existing attribute or add a new attribute named a directly to obj. Code running in strict mode will report errors.
  • If an obj object does not contain an attribute named a, and its prototype chain contains an attribute named a and it is a setter, then the setter will be called, and a will not be added to obj.

  • Implicit shielding may occur in some cases.

var another = { a : 2};
var obj = {};
obj.__proto__ = another;
another.a//Output 2
obj.a//Output 2
obj.hasOwnProperty('a')//Output false
obj.a++;
another.a//Output 2
obj.a//Output 3
obj.hasOwnProperty('a')//Output true

Because obj.a++ is equivalent to obj.a = obj.a + 1, and it is also an assignment statement.

Biprototype chain inheritance and class inheritance (borrow constructor)

One thing to note is that prototype in prototype inheritance refers to the prototype of a function, that is, each function has a prototype attribute corresponding to it. This needs to be distinguished from proto attributes in the prototype chain. The most important thing in prototype inheritance is to inherit the prototype of the parent class to the subclass.

//Parent class Animal
function Animal(name) {
  this.name = name || 'Animal';
}
//Animal public method
Animal.prototype.breath = function() {
    console.log(this.name + ' is breathing!');
};

// Subclass Bird
function Bird(name) {
  //Inheritance of instance attributes by borrowing constructors
  Animal.call(this, name); 
}
//Inheritance of prototype attributes and methods using prototype chains
Bird.prototype = Object.create(Animal.prototype);
//Bird public method
Bird.prototype.fly = function(){
    console.log(this.name + ' is flying!');
}
//Example
var bird = new Bird('birdman');
bird.breath();//birdman is breathing!
bird.fly();//birdman is flying!

The most important sentence in the above code is:
Bird.prototype = Object.create(Animal.prototype);. The Object.create() method creates a new object and associates the proto attribute (prototype chain) inside the object with the specified object (in this case, Animal.prototype).
Note: It's better not to change this sentence into the following two forms

  • Bird.prototype = Animal.prototype;
    This form does not create a new object, but simply points Bird.prototype to Animal.prototype, which is actually an object. When we add new attributes to the prototype of the subclass Bird, this attribute is also equivalent to adding to the prototype of Animal. This leads to redundant attributes when other classes inherit from Animal.
  • Bird.prototype = new Animal();
    This form does create a new object associated with Animal.prototype. But it has many side effects, such as adding redundant attributes to the prototype of Bird, or doing some unnecessary operations, or even affecting the "offspring" of Bird.

Mixing Mechanism of Three Simulated Multiple Inheritance

JavaScript itself does not provide multiple inheritance, and the cost of using multiple inheritance is too high. But we can simulate the implementation of multiple inheritance, that is, the blending mechanism. In jQuery, mixing is called extend. We usually call it mixin. Let's look at a very simple mixin function.

1. Explicit mixing

function mixin(sourceObj, targetObj){
    for(var key in sourceObj){
        if(!(key in targetObj)){
            targetObj[key] = sourceObj[key];
        }
    }
    return targetObj;
}
var Animal = { 
    name : 'Animal',
    breath : function() {
        console.log(this.name + ' is breathing!');
    }
};
//Create an empty object to copy the contents of the Animal
var Bird = mixin(Animal, {});
//Copy new content into bird
mixin({
    name : 'birdman',
    fly : function(){
        console.log(this.name + ' is flying!');
    }
}, Bird);

If you explicitly mix more than one object into the target object, you can mimic multiple inheritance behavior. However, there is still no direct way to deal with the same names of functions and attributes. Some libraries have proposed "late binding" technologies, but these technologies may reduce performance overwhelmingly.

2. Parasitic inheritance

A variant of explicit mixing is called "parasitic inheritance".

//Parent class Animal
function Animal(name) {
  this.name = name || 'Animal';
}

Animal.prototype.breath = function() {
    console.log(this.name + ' is breathing!');
};

// Parasitoid Bird
function Bird(name) {
  var bird = new Animal();

  bird.name = name || 'bird';
  bird.fly = function(){
    console.log(this.name + ' is flying!');
  };
  return bird;
}

//Example
var bird = new Bird('birdman');
bird.breath();//birdman is breathing!
bird.fly();//birdman is flying!

3. Implicit mixing

var Animal = { 
    name : 'Animal',
    breath : function() {
        console.log(this.name + ' is breathing!');
    }
};
var Bird ={
    name : 'birdman',
    breath : function() {
        //Implicitly mix breath into Bird
        Animal.breath.call(this);
    }
};

Posted by cornix on Tue, 02 Apr 2019 23:57:31 -0700