The introduction
In THE js development process, we often cannot get around class inheritance. In THE ES6 era, we only need to implement class inheritance through the extends sugar. In the early ES5, we implement class inheritance through the operation of object prototype chain. Let readers further understand the prototype chain model of JS objects and some design ideas of JS procedures.
Front knowledge
-
Js variable types: Variables in JS can be divided into:
- Value types (basic types) : Contents are stored in stacks, such as String, Number, Boolean, Null, Undefined, Symbol.
- Reference: The content is stored in the heap, and the stack stores the index address of the heap, such as Object, Array, and Function.
-
Every function (class) in js has a prototype property that points to the prototype of the function. Each object can access the methods and properties on that object’s constructor prototype by __proto__. In the process of accessing object attributes and methods, JS will first find whether the object itself has modified methods and attributes. If not, it will find superior objects through __proto__, and form prototype chains through __proto__ linked objects.
Prototype chain inheritance
From the principle of stereotype chain, it is easy to think that we can implement inheritance by binding an instance of a parent class to the stereotype chain of a derived class:
/ / parent class
function SuperClass() {
// Public attributes
this.a = 'I am super class! ';
this.arr = ['a'.'b'.'c'];
}
// Public method
// Methods that generally do not need to be modified can be placed in the prototype chain to prevent the repetition of defining the content of reference types during instantiation, resulting in memory waste
SuperClass.prototype.echoA = function () {
console.log(this.a);
};
/ / a derived class
function SubClass() {
this.b = 'I am sub class! ';
}
// Bind an instance of the parent class to the prototype chain
SubClass.prototype = new SuperClass();
// Public methods on derived classes
SubClass.prototype.echoB = function () {
console.log(this.b);
};
// Instantiate the derived class
const sub = new SubClass();
// Call the parent method
sub.echoA(); // I am super class!
// Call this class method
sub.echoB(); // I am sub class!
Copy the code
Prototype = new SuperClass(); Binding the parent object into the prototype chain of the derived class completes inheritance. However, this inheritance method has the following disadvantages:
-
Since the parent class attribute is bound to the prototype chain, the modified attribute becomes the common attribute of the derived class. When the common attribute of the referenced type in the derived class changes, the objects generated by all instances of the derived class will change:
const sub1 = new SubClass(); const sub2 = new SubClass(); console.log(sub1.arr); // ['a', 'b', 'c'] console.log(sub2.arr); // ['a', 'b', 'c'] sub1.arr.push('d'); console.log(sub1.arr); // [ 'a', 'b', 'c', 'd' ] console.log(sub2.arr); // [ 'a', 'b', 'c', 'd' ] Copy the code
In the example above, we modified the ARR attribute of sub1, resulting in the modification of the ARR attribute of sub2
- When constructing a derived class, passing arguments to the parent constructor is not possible.
Constructor inheritance
In order to solve the defect of stereotype chain inheritance, let’s modify the definition of the code:
/ / parent class
function SuperClass(arg) {
// Public attributes
this.a = 'I am super class! ';
this.arr = ['a'.'b'.'c'];
this.arg = arg;
}
// Public method
// Methods that generally do not need to be modified can be placed in the prototype chain to prevent the repetition of defining the content of reference types during instantiation, resulting in memory waste
SuperClass.prototype.echoA = function () {
console.log(this.a);
};
/ / a derived class
function SubClass(arg) {
this.b = 'I am sub class! ';
// A derived class calls the parent class constructor
SuperClass.call(this, arg);
}
// Public methods on derived classes
SubClass.prototype.echoB = function () {
console.log(this.b);
};
const sub1 = new SubClass('sub1');
const sub2 = new SubClass('sub2');
console.log(sub1.arg); // sub1
console.log(sub2.arg); // sub2
console.log(sub1.arr); // ['a', 'b', 'c']
console.log(sub2.arr); // ['a', 'b', 'c']
sub1.arr.push('d');
console.log(sub1.arr); // [ 'a', 'b', 'c', 'd' ]
console.log(sub2.arr); // [ 'a', 'b', 'c' ]
Copy the code
The SuperClass. Call (this, arg); Inheritance is achieved by forging a parent class constructor in a derived class. However, this inheritance method can only inherit the attribute method of the parent class itself, but cannot inherit the attribute and method of the parent class prototype chain.
Combinatorial inheritance
Prototype = superclass.prototype; subclass.prototype = superclass.prototype; subclass.prototype = SuperClass. , so that derived classes can access property methods on the parent class’s prototype chain. However, since Prototype is a reference type, direct assignment makes the derived class’s prototype point to the same object as its parent. Adding methods to the derived class’s prototype chain affects the parent class:
/ / parent class
function SuperClass(arg) {
// Public attributes
this.a = 'I am super class! ';
this.arr = ['a'.'b'.'c'];
this.arg = arg;
}
// Public method
// Methods that generally do not need to be modified can be placed in the prototype chain to prevent the repetition of defining the content of reference types during instantiation, resulting in memory waste
SuperClass.prototype.echoA = function () {
console.log(this.a);
};
/ / a derived class
function SubClass(arg) {
this.b = 'I am sub class! ';
// Subclasses call the parent constructor
SuperClass.call(this, arg);
}
// Bind the parent's prototype chain to the derived prototype chain
SubClass.prototype = SuperClass.prototype;
// Public methods on derived classes
SubClass.prototype.echoB = function () {
console.log(this.b);
};
// Instantiate the parent class
const superClass = new SuperClass('superClass');
// Prints the subclass prototype on the chain method
console.log(superClass.echoB); // [function]
Copy the code
Prototype = new SuperClass(”); SubClass. Prototype = new SuperClass(”); In this way we combine the advantages of stereotype chain inheritance and constructor inheritance. But there are some flaws in this combinatorial inheritance: the parent class needs to be constructed repeatedly, the derived class binding prototype chain needs to be constructed once, and the instantiation of the derived class needs to be constructed again.
Parasitic inheritance
If we simply want to inherit the attribute method on the parent class’s prototype chain, we can construct a ‘third party’ to bind the parent class’s prototype chain, and then the derived class binds the instance of the ‘third party’ to the prototype chain. In order to save resources, we can use the empty class to act as the ‘third party’ :
// Define empty class as' third party '
function O() {}
// Bind the parent's prototype chain
O.prototype = SuperClass.prototype;
// Bind the 'third party' with the parent's prototype chain into the derived class's prototype chain
SubClass.prototype = new O();
Copy the code
Parasitic combinatorial inheritance
Combining the composite and the parasite neatly circumvent the need to call the redundant parent constructor when the derived body’s prototype chain is bound:
/ / parent class
function SuperClass(arg) {
// Public attributes
this.a = 'I am super class! ';
this.arr = ['a'.'b'.'c'];
this.arg = arg;
}
// Public method
// Methods that generally do not need to be modified can be placed in the prototype chain to prevent the repetition of defining the content of reference types during instantiation, resulting in memory waste
SuperClass.prototype.echoA = function () {
console.log(this.a);
};
/ / a derived class
function SubClass(arg) {
this.b = 'I am sub class! ';
// Subclasses call the parent constructor
SuperClass.call(this, arg);
}
// Define empty class as' third party '
function O() {}
// Bind the parent's prototype chain
O.prototype = SuperClass.prototype;
// Bind the 'third party' with the parent's prototype chain into the derived class's prototype chain
SubClass.prototype = new O();
// Public methods on derived classes
SubClass.prototype.echoB = function () {
console.log(this.b);
};
Copy the code
conclusion
Parasitic combinative inheritance is the perfect inheritance for ES5, and many ES6 and TS implementations of extends sugar are also implemented in this way
Hope that through the study of this article can deepen everyone’s understanding of the essence of JS class, rough writing do not spray, if there are mistakes can be put forward to communicate, thank you for reading.