JavaScript Six ways of inheritance
Many object-oriented languages support Interface inheritance and Implementation inheritance Two ways of inheritance , The former only inherits the method signature , The latter inherits the actual method . Interface inheritance in ECMAScript It's impossible , Because the function has no signature . Implementing inheritance is ECMAScript The only way to support inheritance , This is mainly through Prototype chain Realized .
1. Prototype chain
1.1 Ideas
ECMAScript-262 The prototype chain is defined as ECMAScript The main way to inherit . The basic idea is this Inherit properties and methods of multiple reference types through prototypes .
Constructors 、 Relationship between prototype and instance : Each constructor has a prototype object , The prototype has a property that refers back to the constructor associated with it , And the instance has an internal pointer to the prototype .
Prototype chain : If prototype is an instance of the another type , Then the prototype itself has an internal pointer to another prototype , Correspondingly, another prototype also has a pointer to another constructor . In this way, a prototype chain is constructed between the instance and the prototype .
function SuperType () {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
}
function SubType () {
this.subproperty = false;
}
// Inherit SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subproperty;
}
let instance = new SubType();
console.log(instance.getSuperValue()); // true
SubType
By creating a SuperType
And assign it to its own prototype SubType.prototype
Realized with SuperType
Inheritance , This assignment overrides Subtype
The original prototype , Replace it with SuperType
Example , namely SuperType
All properties and methods that the instance can access will also exist in SubType.prototype
, At the same time instance
( Through internal [[Prototype]]
) Point to SubType.prototype
, and SubType.prototype
( As SuperType
The instance of [[Prototype]]
) Point to SuperType.prototype
.
Be careful :
getSuperValue
The method is stillSuperType.prototype
On the object , andproperty
Property is inSubType.prototype
On . becausegetSuperValue
Is a prototype method , andproperty
Is an instance property .SubType.prototype
Now it isSuperType
An example of , Therefore, it will be stored on itproperty
attribute .- because
SubType.prototype
Ofconstructor
Property is overridden to point toSuperType
, thereforeinstance.constructor
Point toSuperType
. - When inheritance is implemented through the prototype chain , The search process continues up the prototype chain , call
instance.getSuperValue
Will search in turninstance
、SubType.prototype
andSuperType.prototype
. When a property or method cannot be found , The search process never stops until the end of the prototype chain .
1.2 Expand
The default prototype
By default , All reference types inherit from
Object
, This is also achieved through the prototype chain . The default prototype of any function is aObject
Example , That is, this instance has an internal pointer toObject.prototype
. Therefore, custom types can inherit includingtoString
、valueOf
All default methods including .Relationship between prototype and inheritance
The relationship between prototype and inheritance can be determined in two ways :
instanceof
The operator : If the corresponding constructor appears in the prototype chain of an instance , beinstanceof
Operator returntrue
.console.log(instance instanceof Object); // true console.log(instance instanceof SuperType); // true console.log(instance instanceof SubType); // true
isPrototypeOf
Method : When the prototype in the prototype chain calls this method and passes in the instance, it returnstrue.
console.log(Object.prototype.isPrototypeof(instance)); // true console.log(SuperType.prototype.isPrototypeof(instance)); // true console.log(SubType.prototype.isPrototypeof(instance)); // true
About the way
If you want to override the methods of the parent class or add methods that the parent class does not have , It must be added to the prototype after the prototype assignment .
function SuperType () { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType () { this.subproperty = false; } // Inherit SuperType SubType.prototype = new SuperType(); // The new method SubType.prototype.getSubValue = function () { return this.subproperty; } // Overwrite existing methods SubType.prototype.getSuperValue = function () { return false; } let instance = new SubType(); console.log(instance.getSuperValue()); // false
Creating prototypes in the literal way of objects breaks the chain of prototypes , Equivalent to rewriting the prototype chain , Set the prototype to a
Object
Example .function SuperType () { this.property = true; } SuperType.prototype.getSuperValue = function () { return this.property; } function SubType () { this.subproperty = false; } // Inherit SuperType SubType.prototype = new SuperType(); SubType.prototype = { getSubValue () { return this.subproperty; } someOtherMethod () { return false; } } let instance = new SubType(); console.log(instance.getSuperValue()); // error
The problem of prototype chain
The main problem is when the prototype contains reference values , The reference value is shared between instances , This is why attributes are usually defined in constructors rather than on prototypes .
function SuperType () { this.colors = ['red', 'blue', 'green']; } function SubType () {} SubType.prototype = new SuperType(); let instance1 = new SubType(); instance1.colors.push('black'); let instance1 = new SubType(); console.log(instance1.colors); // 'red', 'blue', 'green', 'black' console.log(instance2.colors); // 'red', 'blue', 'green', 'black'
When
SubType
Inheritance through prototypeSuperType
after ,SubType.prototype
becomeSuperType
An example of , So he got his owncolors
attribute , Similar to creatingSubType.prototype.colors
attribute , soSubType
All instances of will share thiscolors
attribute .- When instantiating a subtype, you cannot pass parameters to the constructor of the parent type .
2. Stealing constructors
Stealing constructors (constructor stealing) Technology is also known as object camouflage or classic inheritance .
2.1 Ideas
The basic idea is to call the parent class constructor in the subclass constructor . Because a function is a simple object that executes code in a specific context , So you can use apply
and call
Method executes the constructor in the context of the newly created object .
function SuperType () {
this.colors = ['red', 'blue', 'green'];
}
function SubType () {
// Inherit SuperType
SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push('black');
let instance1 = new SubType();
console.log(instance1.colors); // 'red', 'blue', 'green', 'black'
console.log(instance2.colors); // 'red', 'blue', 'green'
SuperType
The constructor is working for SubType
Execute in the context of a new object created by an instance of , Equivalent to new SubType
Object SuperType
All initialization code in the constructor , That is, each instance will have its own colors
attribute .
2.2 Expand
Pass parameters
The advantage of stealing constructors is that you can pass parameters to the parent constructor in the child constructor .
function SuperType (name) { this.name = name; } function SubType () { SuperType.call(this, 'Stan'); this.age = 24; } let instance = new SubType(); console.log(instance.name); // 'Stan' console.log(instance.age); // 24
stay
SubType
Invocation in constructorSuperType
Pass in parameters when constructor , In fact, it willSubType
Defined on an instance ofname
attribute .To ensure
SuperType
The constructor does not overrideSubType
Properties defined , You can add additional properties to the subclass instance after calling the parent class constructor .problem
The main disadvantage of stealing constructors is also the problem of using constructor patterns to customize types , That is, the method must be defined in the constructor , So functions can't be reused . Besides , Subclasses also cannot access methods defined on the parent prototype , So all types can only use constructor mode .
3. Combination inheritance
Composite inheritance is also called pseudo classical inheritance , It combines the advantages of prototype chain and stealing construction function tree . Combinatorial inheritance makes up for the shortcomings of prototype chain and embezzlement of constructors , yes JavaScript The most used inheritance pattern in . meanwhile , Composite inheritance is also preserved instanceof
Operators, and isPrototypeOf
The ability of methods to recognize synthetic objects .
3.1 Ideas
The basic idea of combinatorial inheritance is to inherit the attributes and methods on the prototype using the prototype chain , By stealing the constructor to inherit instance properties .
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
}
function SubType (name, age) {
// Inheritance attribute
SuperType.call(this, name);
this.age = age;
}
// Inheritance method
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
}
let instance1 = new SubType('Stan', 24);
instance1.colors.push('black');
console.log(instance1.colors); // 'red', 'blue', 'green', 'black'
instance1.sayName(); // 'Stan'
instance1.sayAge(); // 24
let instance2 = new SubType('Greg', 20);
console.log(instance2.colors); // 'red', 'blue', 'green'
instance2.sayName(); // 'Greg'
instance2.sayAge(); // 20
3.2 Expand
Combinatorial inheritance also has efficiency problems , The most important thing is that the parent constructor will be called twice , The first is called when you create a subclass prototype , The second time is to call in the subclass constructor. . Essentially , The subclass prototype ultimately contains all the instance properties of the superclass object , Just override its prototype when the subclass constructor executes .
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
}
function SubType (name, age) {
// Second call
SuperType.call(this, name);
this.age = age;
}
// First call
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
console.log(this.age);
}
After the above code is executed ,SubType.prototype
There will be name
and colors
Two attributes , Both are SuperType
Instance properties for , Now it becomes SubType
Prototype properties of . Calling SubType
Constructor , Will also call SuperType
Constructors , A new object is created on the new object name
and colors
Two instance properties , Two properties with the same name on the prototype will be masked .
The solution to the efficiency problem of combinatorial inheritance is parasitic combinatorial inheritance .
4. Original pattern inheritance
4.1 Ideas
first , Original pattern inheritance (prototypal inheritance) Is an inheritance method that does not involve constructors in the strict sense , The starting point is that information sharing between objects can be realized through prototypes even without custom types .
function object (o) {
function F () {}
F.prototype = o;
return new F();
}
object
Function by creating a temporary constructor , And assign the passed in object to the prototype of this constructor , Finally, an instance of this temporary type is returned , Implementation inheritance . Essentially object
The function performs a shallow copy of the incoming object .
let person = {
name: 'Stan',
friends: ['xiaoming', 'xiaohong']
};
let anotherPerson = object(person);
anotherPerson.name = 'xiaobai';
anotherPerson.friends.push('xiaohei');
let yetAnotherPerson = object(person);
yetAnotherPerson.name = 'xiaohei';
yetAnotherPerson.friends.push('xiaohei');
console.log(person.friends); // 'xiaoming', 'xiaohong', 'xiaobai', 'xiaohei'
Prototype inheritance applies when you have an object and want to create a new object based on it . Pass this object to object
function , Then modify the returned object appropriately .
4.2 Expand
Object.create
MethodECMAScript5 Added
Object.create
Method to standardize the concept of prototype inheritance .Object.create
Method accepts two parameters , Objects as prototypes of new objects , And objects that define additional properties for new objects ( Optional ). When there is only the first parameter ,Object.create
Method and previousobject
The effect of the method is the same .let person = { name: 'Stan', friends: ['xiaoming', 'xiaohong'] } let anotherPerson = Object.create(person); anotherPerson.name = 'xiaobai'; anotherPerson.friends.push('xiaohei'); let yetAnotherPerson = Object.create(person); yetAnotherPerson.name = 'xiaohei'; yetAnotherPerson.friends.push('xiaohei'); console.log(person.friends); // 'xiaoming', 'xiaohong', 'xiaobai', 'xiaohei'
Object.create
The second parameter of the method isObject.defineProperties
The second parameter of the method is the same , Each new attribute is described by its own descriptor .let person = { name: 'Stan', friends: ['xiaoming', 'xiaohong'] } let anotherPerson = Object.create(person, { name: { value: 'Greg' } }); console.log(anotherPerson.name); // 'Greg'
problem
Archetypal inheritance applies to constructors that do not need to be created separately , But there are still situations where information needs to be shared between objects . But like the prototype chain , The reference value contained in the property is always shared between related objects .
5. Parasitic inheritance
Parasitic inheritance (parasitic inheritance) It is a kind of inheritance method that is close to prototype inheritance .
5.1 Ideas
The idea of parasitic inheritance is similar to parasitic constructors and factory patterns , That is, create a function that implements inheritance , Enhance objects in some way , Finally, return this object .
function createAnother (original) {
// Create a new object
let clone = object(original);
// Enhance objects in some way
clone.sayHi = function () {
console.log('hi');
};
return clone;
}
let person = {
name: 'Stan'
}
let anotherPerson = createAnother(person);
anotherPerson.sayHi(); // 'hi'
5.2 Expand
- Parasitic inheritance also applies to the main object of interest , Regardless of the type and constructor scenarios .
object
Functions are not required for parasitic inheritance , You can use any function that returns a new object .- Adding functions to objects through parasitic inheritance can make functions difficult to reuse , Similar to the constructor pattern .
6. Parasitic combination inheritance
Parasitic composite inheritance inherits properties by stealing constructors , The inheritance method uses a hybrid chain .
The basic idea of parasitic composite inheritance is not to assign a value to the subclass prototype by calling the parent class constructor , Instead, take a copy of the parent prototype , That is, parasitic inheritance is used to inherit the parent class prototype , Then assign the returned new object to the subclass prototype .
function inheritPrototype (SubType, SuperType) {
let prototype = object(SuperType.prototype);
prototype.constructor = SubType;
SubType.prototype = prototype;
}
inheritPrototype
Function implements the core logic of parasitic combinatorial inheritance . The function receives two parameters: the subclass constructor and the parent constructor . Inside the function , First, create a copy of the parent class prototype ; The second is to return prototype
Object settings constructor
attribute , Solve the default problem caused by rewriting the prototype constructor
Lost problems ; Assign the newly created object to the prototype of the subtype .
function SuperType (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType (name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
console.log(this.age);
};
When using parasitic composite inheritance , The prototype chain remains the same ,instanceof
Operators, and isPrototypeOf
The method is normal and effective . Parasitic composite inheritance is the best mode of reference type inheritance .