Inheritance is the basic concept of object-oriented language. Generally OO language supports two ways of inheritance: interface inheritance and implementation inheritance. Interface inheritance only inherits method signature, while implementation inheritance inherits the actual method. The function in ECMAScript is not signed, so interface inheritance cannot be implemented. ECMAScript only supports implementation inheritance, which is mainly implemented by prototype chain.
1, Prototype chain
Prototype chain is the main method to implement inheritance. Its basic idea is to use prototype to let one reference type inherit the properties and methods of another reference type.
The simple code of inheritance using the idea of prototype chain is as follows:
function Parent() { this.name = 'zhangsan'; this.children = ['A', 'B']; } Parent.prototype.getChildren = function() { console.log(this.children); } function Child() { } Child.prototype = new Parent(); var child1 = new Child(); child1.children.push('child1') console.log(child1.getChildren()); // Array ["A", "B", "child1"] var child2 = new Child(); child2.children.push('child2') console.log(child2.getChildren()); // Array ["A", "B", "child1", "child2"] Copy code
By pointing the prototype of the subclass to an instance of the parent class to implement inheritance, notice that the constructor of the subclass points to the parent class
The main problem of prototype chain inheritance: the attribute of reference type is shared by all instances. When creating Child's instance, the parameter cannot be passed to Parent
2, Borrow constructor
In order to solve the problem of including reference type value in the prototype, people began to use a technology called borrowing constructor to implement inheritance. The basic idea of this technology is very simple, that is to call the superclass constructor inside the subclass constructor
function Parent(age) { this.names = ['lucy', 'dom']; this.age = age; this.getName = function() { return this.name; } this.getAge = function() { return this.age; } } function Child(age) { Parent.call(this, age); } var child1 = new Child(18); child1.names.push('child1'); console.log(child1.names); // [ 'lucy', 'dom', 'child1' ] var child2 = new Child(20); child2.names.push('child2'); console.log(child2.names); // [ 'lucy', 'dom', 'child2' ] Copy code
Advantages: avoid that the attribute of reference type is shared by all instances, and can directly pass parameters to Parent in Child. Disadvantages: methods are defined in the constructor, and each time an instance is created, a method will be created once, so function reuse is impossible
3, Combination inheritance
Combinatorial inheritance is a kind of inheritance mode that combines prototype chain and borrowing constructors to give full play to their advantages. The idea behind it is to use prototype chain to inherit prototype properties and methods, and to borrow constructors to inherit instance properties. In this way, it can not only ensure that the function reuse can be realized through the prototype definition method, but also ensure that each instance has its own attributes.
function Parent(name, age) { this.name = name; this.age = age; this.colors = ['red', 'green'] console.log('parent') } Parent.prototype.getColors = function() { console.log(this.colors); } function Child(name, age, grade) { Parent.call(this, name, age);// Once when creating a subclass instance this.grade = grade; } Child.prototype = new Parent(); // Specifies that the subclass prototype will execute once Child.prototype.constructor = Child;// Correction constructor Child.prototype.getName = function() { console.log(this.name) } var c = new Child('alice', 10, 4) console.log(c.getName()) > "parent" > "parent" > "alice" Copy code
Combinatorial inheritance has the characteristics that prototype chain inheritance can reuse functions, and borrowing constructor can ensure that each subclass instance can have its own properties and pass parameters to the superclass. However, combinatorial inheritance is not the perfect way to implement inheritance, because this way will call the superclass constructor twice when creating a subclass.
4, Archetypal inheritance
This method does not use the constructor in strict sense. The idea is that with the help of prototype, new objects can be created based on existing objects without creating custom types.
function object(o) { function F(){}; F.prototype = o; return new F(); } Copy code
Inside the object() function, a temporary constructor is created first, then the incoming object is used as the prototype of the constructor, and finally a new instance of the temporary type is returned. In essence, object () completes a shallow copy operation
var person = { name: 'alice', friends: ['leyla', 'court', 'van'] } var p1 = object(person); p1.name = 'p1'; p1.friends.push('p1'); var p2 = object(person); p2.name = 'p2'; p2.friends.push('p2'); console.log(p1.name) console.log(person.friends) > Array ["leyla", "court", "van", "p1", "p2"] Copy code
ECMAScript5 normalizes prototype inheritance by adding the Object.create() method, which takes two parameters: an object used as the prototype of the new object and an object defining properties for the new object
var person = { name: 'alice', friends: ['leyla', 'court', 'van'] } var p1 = Object.create(person); p1.name = 'p1'; p1.friends.push('p1'); var p2 = Object.create(person); p2.name = 'p2'; p2.friends.push('p2'); console.log(p1.name) console.log(person.friends) > Array ["leyla", "court", "van", "p1", "p2"] Copy code
5, Parasitic inheritance
Parasitic inheritance is an idea closely related to prototype inheritance, that is, to create a function only used to encapsulate the inheritance function process, which enhances the object internally in some way, and finally returns the object.
function object(obj) { function F(){}; F.prototype = obj; return new F(); } function createAnother(original) { var clone = object(original); // create new object clone.sayHi = function(){ console.log('hello, world'); // Enhance objects, add properties or methods } return clone; // Return to new object } var person = { name: 'alice', friends: ['Sherly', 'Taissy', 'Vant'] } var p1 = createAnother(person); p1.sayHi(); > "hello, world" Copy code
The new object not only has the properties and methods of person object, but also has its own sayHi() method defect: using parasitic inheritance to add functions to objects will reduce the efficiency due to the failure of function reuse, which is similar to the constructor mode
6, Parasitic combinatorial inheritance
Combinatorial inheritance is the most commonly used inheritance mode in JavaScript. The biggest problem is that the superclass constructor will be called twice no matter what the situation is: once when creating the subclass prototype, once inside the subtype constructor. The subtype will eventually contain all the instance properties of the superclass. The so-called parasitic combinatorial inheritance is to inherit attributes through constructors and inherit methods through prototype chains. The basic idea behind it is that we don't need to call the constructor of a superclass to specify the prototype of a subclass. All we need is a copy of the prototype of a superclass. The basic pattern of parasitic combination inheritance is as follows:
// Copy prototype object of parent class function create(original) { function F(){}; F.prototype = original; return new F(); } // Create a prototype copy of the parent class, change the prototype of the child class, and correct the constructor function inherit(subClass, superClass) { var parent = create(superClass.prototype); parent.constructor = subClass; subClass.prototype = parent; } Copy code
The inherit() function implements the simplest form of parasitic combinatorial inheritance, with two parameters: the subclass constructor and the parent constructor. There are three steps in the function: the first step is to create a copy of the superclass prototype, the second step is to add the constructor property to the created copy, so as to make up for the default constructor property lost due to rewriting the prototype, and the last step is to assign the newly created object to the prototype of the subclass. Therefore, you can use this function to replace the statement in the combination inheritance that assigns the subclass prototype, that is:
Child.prototype = new Parent(); Copy code
Complete inheritance example:
function Parent(name, age){ this.name = name; this.age = age; console.log('parent') } Parent.prototype.getName = function(){ return this.name; } function Child(name, age, grade){ Parent.call(this, name, age); this.grade = grade; } // The way of parasitic combination // Copy prototype object of parent class function create(original) { function F(){}; F.prototype = original; return new F(); } // Create a prototype copy of the parent class, change the prototype of the child class, and correct the constructor function inherit(subClass, superClass) { var parent = create(superClass.prototype); parent.constructor = subClass; subClass.prototype = parent; } inherit(Child, Parent); var child = new Child('lucy', 12, 5); > "parent" Copy code
The high efficiency of parasitic combinatorial inheritance lies in that it only calls the super class constructor once, and at the same time, it can keep the prototype chain unchanged. It is generally considered that parasitic combinatorial inheritance of instanceof and isPrototypeOf() is the most ideal inheritance method of reference type
7, Enhanced parasitic combination inheritance
Parasitic combinatorial inheritance can perfectly implement inheritance, but it is not without disadvantages. In the inherit() method, the prototype of the parent class is copied and assigned to the child class. If there are self-defined methods on the child class prototype, they will also be overwritten. Therefore, the properties or methods defined on the child class prototype can be added to the copied prototype object by the way of Object.defineProperty. In this way, the integrity of the prototype object of the child class can be preserved and the prototype of the parent class can be copied.
function Parent(name, age){ this.name = name; this.age = age; } Parent.prototype.getName = function(){ console.log(this.name) } function Child(name, age, grade){ Parent.call(this, name, age); this.grade = grade; } // Child.prototype = new Parent(); // function inherit(child, parent){ // let obj = parent.prototype; // obj.constructor = child; // child.prototype = obj; // } function inherit(child, parent){ let obj = parent.prototype; obj.constructor = child; for(let key in child.prototype){ Object.defineProperty(obj, key, { value: child.prototype[key] }) } child.prototype = obj; } Copy code
Comparison of several methods:
Inheritance method | advantage | defect |
---|---|---|
Prototype chain inheritance | Function reuse can be realized | 1. Attribute of reference type is shared by all instances; 2. Parameter cannot be passed to superclass when creating subclass |
Borrow constructor | 1. Avoid the attribute of reference type being shared by all instances; 2. Pass parameters to the superclass in the subclass | Methods are defined in the constructor. Every time an instance is created, a method will be created once. Function reuse cannot be realized |
Combination inheritance | It combines the advantages of prototype chain inheritance and constructor, and is the most commonly used inheritance mode in Javascript | Creating a subclass calls the constructor of the superclass twice |
Archetypal inheritance | Archetypal inheritance is good when there's no need to create constructors, just to keep one object similar to another | Properties of reference type will be shared by all instances |
Parasitic inheritance | Can enhance objects | Using parasitic inheritance to add functions to objects will reduce the efficiency due to the failure of function reuse, which is similar to the constructor pattern; at the same time, there is a defect that the attribute of reference type is shared by all instances |
Parasitic combinatorial inheritance | It copies the prototype of the superclass without calling the superclass constructor. It can not only realize function reuse, but also avoid that the instance of the reference type is shared by the subclass. At the same time, it only needs to call the superclass constructor once to create the subclass | - |