Basic knowledge of

Javascript relies on prototype chain for attribute search. When calling an object’s attribute, JS will search step by step up the chain until it finds the corresponding attribute or reaches the top-level object (NULL), for example (A is A constructor, A is an instance generated by the constructor) :

function A(){}
const a = new A()
Copy the code
  • A js Object has a constructor function and a prototype Object (which can be obtained via the __proto__ property or the object.getProtoTypeof method).
  • The function itself is an object in JS.
  • The function has a Prototype property, which itself is a JS object, and the Prototype object’s constructor points to the function itself. When calling a function with the new keyword, js first creates an empty object ({}) and points its prototype object to the function’s prototype property, then points this of the function body to the new empty object, and returns the object after executing the function body code.
  • The prototype object of the instance object points to the Prototype property of the constructor that generated the instance object.

In order to facilitate viewing, we can map the last node to other graphs, which is actually the relationship on one graph (the bold line indicates the equality relationship, and the arrow indicates that the preceding object has the following attributes).

graph LR
a --> a.constructor===A
a --> a.__proto__===A.prototype
A --> A.prototype
A.prototype --> A.prototype.__proto__===Object.prototype
A.prototype --> A.prototype.constructor===A
A --> A.constructor===Funciton
A --> A.__proto__===Function.prototype
graph LR
Funciton --> Function.prototype
Function.prototype -->Function.prototype.constructor===Funciton
Function.prototype -->Function.prototype.__proto__===Object.prototype
Funciton === Function.constructor
Funciton --> Function.__proto__===Function.prototype
graph LR
Object --> Object.prototype
Object --> Object.constructor===Funciton
Object.prototype --> Object.prototype.__proto__===null
Object --> Object.__proto__
Object.__proto__ --> Object.__proto__.constructor===Function
Object.__proto__ --> Object.__proto__.__proto__
Object.__proto__.__proto__ --> Object.__proto__.__proto__.__proto__===null
Object.__proto__.__proto__ --> Object.__proto__.__proto__.constructor===Object
Object.prototype --> Object.prototype.constructor===Object

A as an instance object is generated by the function A, which has constructor and a prototype object.

console.log(a.constructor === A) // true
console.log(a.__proto__===A.prototype) // true
Copy the code

A is A function and is itself an instance object;

console.log(A instanceof Object) // true
Copy the code

A is generated by Function (constructor) and has A constructor and prototype object; A is Function (prototype object points to function.prototype);

console.log(A.constructor === Function) // true
console.log(Function.__proto__===Function.prototype) // true
Copy the code

A. protoType is an Object whose constructor points to A itself. The prototype Object points to Object.prototype.

console.log(A.prototype.constructor === A) // true
console.log(A.prototype.__proto__===Object.prototype) // true
Copy the code

Javascript inheritance

Prototype chain inheritance

The prototype Object in the constructor is __proto__ (Object. GetPrototypeOf), and the js attribute is obtained up the prototype chain until it reaches the attribute or the top-level null. So you can modify the constructor’s Prototype object to extend the properties

function Parent(name) {
  this.name = name;
}
Parent.prototype.getName = function () {
  return this.name;
}

function Child(desc) {
  this.desc = desc;
}
const parent = new Parent('parentName');
Child.prototype = parent;
const child = new Child("This is an instance of a subclass.");
console.log(child.getName(), child.desc); // parentName This is an instance of a subclass
Copy the code

The drawback of stereotype chain inheritance is that if a subclass instance changes the reference class properties (such as objects, arrays, etc.) of the inherited instance, the attributes of all subclass instances will be modified:

function Parent(name) {
  this.name = name;
  this.type = "initType";
  this.obj = { key: 'key'.value: 'value' }; // Reference type
  this.getName = function () {
    return this.name;
  }
}
Parent.prototype.getName = function () {
  return this.name;
}

function Child(desc) {
  this.desc = desc;
}

const parent = new Parent('parentName');
Child.prototype = parent;
const child1 = new Child('desc1');
const child2 = new Child('desc2');

child1.type = 'child1';
child1.obj.customProperty = 'child1';

console.log(child1.getName(), child1.desc, child1.type, child1.obj); // parentName desc1 child1 { key: 'key', value: 'value', customProperty: 'child1' }
console.log(child2.getName(), child2.desc, child2.type, child2.obj); // parentName desc2 initType { key: 'key', value: 'value', customProperty: 'child1' }
Copy the code

Borrow constructor inheritance

Inherited the prototype chain can see no way in the instance initialization time to modify the inherited attributes (subclass to the parent class constructor arguments), only can access and modify in the instance, if you change the value of the reference type as, cause all subclass instances are modified bugs, because it is a Shared a parent class instance prototype as a subclass object. You can solve these problems by borrowing the constructor of the parent class

function Parent(name) {
  this.name = name;
  this.type = "initType";
  this.obj = { key: 'key'.value: 'value' }; // Reference type
  this._getName = function () {
    return this.name;
  }
}
Parent.prototype.getName = function () {
  return this.name;
}

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

const child1 = new Child('name1'.'desc1');
const child2 = new Child('name2'.'desc2');

child1.type = 'child1';
child1.obj.customProperty = 'child1';

// Notice that _getName() is used because getName does not exist
console.log(child1._getName(), child1.desc, child1.type, child1.obj); // name1 desc1 child1 { key: 'key', value: 'value', customProperty: 'child1' }
console.log(child2._getName(), child2.desc, child2.type, child2.obj); // name2 desc2 initType { key: 'key', value: 'value' }
// console.log(child1.getName()); // TypeError: child1.getName is not a function
// console.log(child2.getName()); // TypeError: child1.getName is not a function
Copy the code

The drawback of borrowing constructors is that you can’t reuse methods and access properties in prototype because with constructors all methods are specified in the constructor and there’s no extension at all (so instead of using getName in the above example, you create _getName in the constructor). The essence of borrowing a constructor is to execute the parent constructor in a subclass, and then refer this in the parent constructor to the current subclass, so that the parent’s prototype object has no relationship to the child’s, resulting in the child instance not being able to access the parent’s prototype object.

Combination of inheritance

Combining the advantages and disadvantages of the above two methods can almost achieve the inheritance effect we need to achieve, which is also the most commonly used inheritance pattern in javascript. Composite inheritance is the value of combining the two methods of prototype chain and borrowing constructor, so as to play the long inheritance pattern of the two. Note: The value of a reference type can only be in the constructor. If you write it in the parent class stereotype, you will still have the problem of all the other child class instances changing after one parent class instance changes (sharing the same parent class stereotype, just reuse the method).

function Parent(name) {
  this.name = name;
  this.type = "initType";
  this.obj = { key: 'key'.value: 'value' }; // Reference type
}
Parent.prototype.getName = function () {
  return this.name;
}

function Child(name, desc) {
  Parent.call(this, name);
  this.desc = desc;
}
Child.prototype = new Parent();

const child1 = new Child('name1'.'desc1');
const child2 = new Child('name2'.'desc2');

child1.type = 'child1';
child1.obj.customProperty = 'child1';

console.log(child1.getName(), child1.desc, child1.type, child1.obj); // name1 desc1 child1 { key: 'key', value: 'value', customProperty: 'child1' }
console.log(child2.getName(), child2.desc, child2.type, child2.obj); // name2 desc2 initType { key: 'key', value: 'value' }
Copy the code

Primary inheritance

Create creates a new Object from an existing Object with the help of a prototype, just as the object. create method does when only the first argument is passed:

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}
Copy the code

Through the prototype chain plus a temporary, in the form of the constructor, the final returns a new object generated by the temporary constructor, read for the properties to the prototype chain, but for the properties of modified because return is a new object is equivalent to directly add an attribute value to a new object, but to modify the prototype of a value of a property of a reference type, The disadvantages of prototype chain inheritance will also be reflected, and all other instance objects will be modified after modification:

function object(o) {
  function F() { }
  F.prototype = o;
  return new F();
}

const parent = {
  type: "parentType".color: ["red"."green"."blue"],}const o1 = object(parent);
const o2 = object(parent);
console.log(o1, o2); / / {} {}
console.log(Object.getPrototypeOf(o1) === Object.getPrototypeOf(o2), Object.getPrototypeOf(o1)); // true { type: 'sourceType', color: [ 'red', 'green', 'blue' ] }
o1.type = 'o1Type';
console.log(o1, o2); // { type: 'o1Type' } {}
o1.color[0] = 'o1 hohoho';
console.log(o1, o2); // { type: 'o1Type' } {}
console.log(Object.getPrototypeOf(o1) === Object.getPrototypeOf(o2), Object.getPrototypeOf(o1)); // true { type: 'sourceType', color: [ 'o1 hohoho', 'green', 'blue' ] }
console.log(o2.type, o2.color); // parentType [ 'o1 hohoho', 'green', 'blue' ]
Copy the code

Parasitic inheritance

Returns a new object by overlaying properties or methods on top of the original type inheritance (factory mode)

function object(o) {
  function F() { }
  F.prototype = o;
  return new F();
}

const parent = {
  type: "sourceType".color: ["red"."green"."blue"],}function proxyFactory(o) {
  const clone = object(o);
  clone.getPrototypeObj = function () {
    return Object.getPrototypeOf(clone);
  }
  return clone;
}

const child = proxyFactory(parent);
console.log(child.getPrototypeObj()); // { type: 'sourceType', color: [ 'red', 'green', 'blue' ] }
Copy the code

Parasitic combinatorial inheritance

Composite inheritance almost perfectly implements JS inheritance, but it does require that the constructor of the parent class be called twice (once when an instance of the parent class is generated and once when the constructor is borrowed). The constructor calls generated in this step are overwritten by the constructor calls generated in the following step. We only use the prototype object of the parent class instance (the prototype of the parent class constructor) and do not need it to generate the attributes, so we can remove the constructor calls in this step:

function Parent(name) {
  this.name = name;
  this.type = "initType";
  this.obj = { key: 'key'.value: 'value' }; // Reference type
}
Parent.prototype.getName = function () {
  return this.name;
}

function Child(name, desc) {
  Parent.call(this, name);
  this.desc = desc;
}
Child.prototype = Parent.prototype; // Change new Parent() to parent.prototype

const child1 = new Child('name1'.'desc1');
const child2 = new Child('name2'.'desc2');

child1.type = 'child1';
child1.obj.customProperty = 'child1';

console.log(child1.getName(), child1.desc, child1.type, child1.obj); // name1 desc1 child1 { key: 'key', value: 'value', customProperty: 'child1' }
console.log(child2.getName(), child2.desc, child2.type, child2.obj); // name2 desc2 initType { key: 'key', value: 'value' }
Copy the code

Parent() : new Parent(); Parent() : new Parent(); Parent() : new Parent();

function Parent(name) {
  this.name = name;
  this.type = "initType";
  this.obj = { key: 'key'.value: 'value' }; // Reference type
}
Parent.prototype.getName = function () {
  return this.name;
}

function Child(name, desc) {
  Parent.call(this, name);
  this.desc = desc;
}
Child.prototype = Parent.prototype; // Change new Parent() to parent.prototype
Child.prototype.getDesc = function () {
  return this.desc;
}
const child1 = new Child('name1'.'desc1');
const child2 = new Child('name2'.'desc2');

child1.type = 'child1';
child1.obj.customProperty = 'child1';

console.log(child1.getName(), child1.getDesc(), child1.type, child1.obj); // name1 desc1 child1 { key: 'key', value: 'value', customProperty: 'child1' }
console.log(child2.getName(), child2.getDesc(), child2.type, child2.obj); // name2 desc2 initType { key: 'key', value: 'value' }

const parentInstance = new Parent('parent');
// The prototype object of the parent instance is also forced to join the getDesc method
console.log(Object.getPrototypeOf(parentInstance)); // { getDesc: [Function (anonymous)], getName: [Function (anonymous)] }
Copy the code

So just copy the Parent. Prototype object and give it to child-prototype as its own object. That’s what the original type inheritance does. The combination of parasitic inheritance and combinatorial inheritance gives rise to parasitic combinatorial inheritance.

function object(o) {
  function F() { }
  F.prototype = o;
  return new F();
}
function proxyFactory(superFun, subFun, propertySettionCallback) {
  const clonePrototype = object(superFun.prototype);
  The constructor of the prototype should be equal to itself
  clonePrototype.constructor = subFun;
  subFun.prototype = clonePrototype;
  propertySettionCallback(subFun.prototype);
  return subFun;
}

function Parent(name) {
  this.name = name;
  this.type = "initType";
  this.obj = { key: 'key'.value: 'value' }; // Reference type
}
Parent.prototype.getName = function () {
  return this.name;
}

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


Child = proxyFactory(Parent, Child, function (prototype) {
  prototype.getDesc = function () {
    return this.desc; }})const child1 = new Child('name1'.'desc1');
const child2 = new Child('name2'.'desc2');

child1.type = 'child1';
child1.obj.customProperty = 'child1';

console.log(child1.getName(), child1.getDesc(), child1.type, child1.obj); // name1 desc1 child1 { key: 'key', value: 'value', customProperty: 'child1' }
console.log(child2.getName(), child2.getDesc(), child2.type, child2.obj); // name2 desc2 initType { key: 'key', value: 'value' }

const parentInstance = new Parent('parent');
console.log(Object.getPrototypeOf(parentInstance)); // { getName: [Function (anonymous)] }
Copy the code

The prototype of the parent class does not have the attributes or methods added by the subclass, and the problem of the parent class being inherited by multiple subclasses is also solved. Parasitic combinatorial inheritance is generally considered to be the most ideal inheritance mode.