The inheritance of JavaScript is based on prototype implementation. In the previous prototype , the author talked about prototype inheritance, and introduced in detail the two methods of explicit prototype inheritance and implicit prototype inheritance. Now we cut in from the perspective of inheritance, and introduce 8 common JavaScript inheritance methods in the form of cases
Before reading this, it is recommended to read new first, or remember a sentence, the prototype chain relationship formed by the new keyword is: 实例.__proto__ === 构造函数.prototype
Prototype chain inheritance
function Person(){
this.brain = 'smart'
}
Person.prototype.getBrain = function () {
console.log(this.brain)
}
Person.prototype.age = 100;
Person.prototype.like = {
color: 'red',
}
function JoestarFamily(name) {
this.name = name
this.sayName = function() {
console.log(this.name)
}
}
JoestarFamily.prototype = new Person()
// 等同于 JoestarFamily.prototype === 实例.__proto__ === Person.prototype
var johnny = new JoestarFamily('johnny')
// 等同于 johnny.__proto__ === JoestarFamily.prototype
// 也就是说 johnny.__proto__.__proto__ === Person.prototype
var elaine = new JoestarFamily('elaine')
console.log(johnny, elaine)
Prototype chain inheritance uses the new keyword to point the prototype of JoestarFamily to the instance of Person, that is, the prototype of the subclass points to the instance of the parent class, which is equivalent to JoestarFamily.prototype === 实例.__proto__ === Person.prototype
, when it is new JoestarFamily
, it is quite In johnny.__proto__ === JoestarFamily.prototype
, according to the "equivalent exchange principle" JoestarFamily.prototype
is a common value, after conversion, we can get: johnny.__proto__.__proto__ === Person.prototype
Its prototype chain structure is as follows:
也许你会感到奇怪,怎么JoestarFamily 无了, JoestarFamily.prototype
new Person
, JoestarFamily.prototype
上的constructor
也Gone
Earlier, we talked about the basic types can be copied directly in what JavaScript consists of , while the reference types cannot be copied directly, but the reference address, so we wrote an article - the secret of copying to realize the copying of objects
In prototype chain inheritance, if there is an object on the prototype (such as a like object), all instances will be modified accordingly:
johnny.age = 1;
console.log(johnny) // 1
console.log(elaine.age) // 100
johnny.like.color = 'yellow';
console.log(johnny.like.color) // yellow
console.log(elaine.like.color) // yellow
advantage:
- Parent class/parent class prototype adds new properties and methods, and subclass instances can access
- Simple and easy to implement
shortcoming:
- Multiple inheritance is not possible
- The reference properties of the prototype object are shared by multiple instances, whether private or public
- Create a subclass instance, cannot pass parameters like the parent class constructor
Borrowing Constructor Inheritance (Classic Inheritance)
The key to this method is to call the constructor of the parent class through methods such as call/apply in the constructor of the child class
The principle is the application of this
function Person(brain) {
this.brain = brain;
this.others = {
other1: 1,
other2: 2
};
this.setBrain = function () {
console.log("set brain");
}
}
Person.prototype.getBrain = function () {
console.log(this.brain)
}
Person.prototype.age = 100;
Person.prototype.like = {
color: 'red',
}
function JoestarFamily(name) {
this.name = name
this.sayName = function() {
console.log(this.name)
}
Person.call(this, "smart")
}
var johnny = new JoestarFamily('johnny')
var elaine = new JoestarFamily('elaine')
console.log(johnny, elaine)
See, all the properties and methods are on the instance, the properties and methods in the Person constructor and the properties and methods on JoestarFamily all act on the instances johnny and elaine
My understanding is "use doctrine":
Call Ye Hao, apply Worth mentioning, their role is to modify the point of this. Here, the constructor JoestarFamily calls Person.call(this, "smart")
, which means:
function JoestarFamily(name) {
this.name = name
this.sayName = function() {
console.log(this.name)
}
this.brain = "smart";
this.others = {
other1: 1,
other2: 2
};
this.setBrain = function () {
console.log("set brain");
}
}
In this way, the properties of the two instances do not interfere with each other, and there is no need to modify the object value on the prototype chain and affect other instances.
johnny.others.other1 = 123;
console.log(johnny.others.other1) // 123
console.log(elaine.others.other1 ) // 1
Note: The so-called inheritance is to inherit the properties and methods of the parent class. If you add an object property to the subclass prototype and modify a value in the object property, it will still affect all instances
But the subclass instances johnny and elaine cannot inherit the properties and methods on Person.prototype (after all, there is no inheritance, just take the properties and methods in Person), as follows:
johnny.getBrain() // Uncaught TypeError: johnny.getBrain is not a function
johnny.age // undefined
advantage:
- Fixed an issue where subclass instances in the prototype chain shared parent class reference properties
- Create a subclass instance, you can pass parameters to the parent class
- Multiple inheritance can be achieved (call multiple parent class objects)
shortcoming:
An instance is not an instance of the parent class, just an instance of the child class
- That is
johnny instanceof JoestarFamily
is true -
johnny instanceof Person
is false - Because it just borrows the functions and methods of the parent class instead of inheriting it
- That is
- You can only inherit the properties and methods of the parent class, not the prototype properties and methods of the parent class
- Occupy memory, each subclass has the properties and methods of the parent class (exactly the same), affecting performance
Prototype chain + borrowed constructor combined inheritance
Both want and want, both fish and bear's paws want to get. Not only want to use the prototype chain (extract public methods to the prototype, reduce memory overhead), but also want the instance to call the prototype object properties without affecting other instances
How to do it?
function Person(brain) {
this.brain = brain;
this.others = {
other1: 1,
other2: 2
};
this.setBrain = function () {
console.log("set brain");
}
}
Person.prototype.getBrain = function () {
console.log(this.brain)
}
Person.prototype.age = 100;
Person.prototype.like = {
color: 'red',
}
function JoestarFamily(name) {
this.name = name
this.sayName = function() {
console.log(this.name)
}
Person.call(this, "smart")
}
JoestarFamily.prototype = new Person();
// 等同于 JoestarFamily.prototype === 实例.__proto__ === Person.prototype
JoestarFamily.prototype.constructor = JoestarFamily; // 原型的 constructor 指回原来的构造函数
JoestarFamily.prototype.sayHello = function() {}
var johnny = new JoestarFamily('johnny')
var elaine = new JoestarFamily('elaine')
console.log(johnny, elaine)
Its prototype chain relationship diagram is as follows:
In this way, we see a clearly structured inheritance pattern.
It is compared with prototype chain inheritance: because calling call in the subclass constructor obtains the properties in the parent class constructor (borrowing the constructor inheritance), it will now find its own properties when instantiating, and these values are unique ;
johnny.others.other1 = 123
console.log(johnny.others.other1); // 123
console.log(elaine.others.other1); // 1
Compared with borrowing constructor inheritance: JoestarFamily's prototype inherits Person's prototype and can use properties and methods on Person's prototype
johnny.getBrain() // smart
johnny.age // 100
This method combines the advantages of prototype chain inheritance and borrowed constructor inheritance. It is the most common inheritance pattern in JavaScript, but it also has the disadvantage that the parent class constructor will be called twice at any time.
Once is when setting the subclass prototype:
JoestarFamily.prototype = new Person();
Once is when creating an instance of a subclass:
var johnny = new JoestarFamily('johnny')
// Person.call(this, "smart")
advantage:
- You can inherit the properties and methods of the parent class, and you can also inherit the properties and methods of the parent class prototype
- No reference data sharing issues
- Can be passed to parent class constructor
- Functions can be reused
shortcoming:
- The constructor is called twice, and two instances are generated (causing unnecessary memory overhead)
prototypal inheritance
The author once introduced in the prototype that prototypal inheritance is divided into explicit prototypal inheritance and implicit prototypal inheritance. Implicit prototypal inheritance is implemented for us inside the language, while explicit prototypal inheritance requires us to implement it.
Object.create
function Person(brain) {
this.brain = brain;
this.others = {
other1: 1,
other2: 2
};
this.setBrain = function () {
console.log("set brain");
}
}
Person.prototype.getBrain = function () {
console.log(this.brain)
}
Person.prototype.age = 100;
Person.prototype.like = {
color: 'red',
}
function JoestarFamily(name) {
this.name = name
this.sayName = function() {
console.log(this.name)
}
}
JoestarFamily.prototype = Object.create(Person.prototype); // 重新赋值原型,原先 JoestarFamily.prototype 上的 constructor 被抹除
JoestarFamily.prototype.constructor = JoestarFamily // 指回构造函数
var johnny = new JoestarFamily('johnny')
var elaine = new JoestarFamily('elaine')
console.log(johnny, elaine)
The relationship of the prototype chain is as follows:
The key lies in the difference between Object.create and new. I still have to take the trouble to say one more sentence:
- The prototype chain relationship brought by new is:
实例.__proto__ === 构造函数.prototype
- Object.create is:
实例.__proto__ === 传入的对象
The object passed in in this case is Person.prototype, so there is johnny.__proto__ === Person.prototype
, which inherits from the passed in object, unlike the first three inheritances, inheriting the prototype of the constructor (because of new)
console.log(johnny.__proto__) // === Person.prototype
console.log(johnny.others.other1) // Cannot read properties of undefined (reading 'other1')
If we pass in Person, there will be another wonderful relationship
So the object created by Object.create can inherit the properties and methods of the incoming object
The constructor is used in the appeal example. The properties and methods in the constructor are controlled by this, so they cannot be inherited.
advantage:
- Easy to understand inheritance
shortcoming:
- After the prototype is reassigned, the property constructor needs to be reassigned back
- Inheritance between (sub) classes and (parent) classes cannot be realized, only object inheritance can be realized
- The reference properties of the prototype object will be shared by multiple instances, whether private or public
- Properties and methods in constructors cannot be inherited
Object.setPrototypeOf
Everyone knows Li Qingzhao, but no one misses me Zhu Shuzhen
Presumably Object.setPrototypeOf will say like Zhu Shuzhen, I was only born a few years later, "talent" is no worse than Object.create, why no one remembers me
function Person(brain) {
this.brain = brain;
this.others = {
other1: 1,
other2: 2
};
this.setBrain = function () {
console.log("set brain");
}
}
Person.prototype.getBrain = function () {
console.log(this.brain)
}
Person.prototype.age = 100;
Person.prototype.like = {
color: 'red',
}
function JoestarFamily(name) {
this.name = name
this.sayName = function() {
console.log(this.name)
}
}
Object.setPrototypeOf(JoestarFamily.prototype, Person.prototype);
var johnny = new JoestarFamily('johnny')
var elaine = new JoestarFamily('elaine')
console.log(johnny, elaine)
Its prototype chain relationship diagram:
It is different from Object.create in that it can pass in two objects, so that we can make the prototype of the subclass inherit from the prototype of the parent class to achieve inheritance
advantage:
- Easy to understand inheritance
shortcoming:
- The reference properties of the prototype object will be shared by multiple instances, whether private or public
- Properties and methods in constructors cannot be inherited
parasitic inheritance
As the name suggests, create a function that internally enhances the object in some way, and finally returns the object
function createObj(obj) {
var clone = Object.create(obj)
clone.sayHello = function() {
console.log('hello')
}
return clone
}
let person = {
name: 'johnny',
age: 22
}
let anotherPerson = createObj(person)
anotherPerson.sayHello()
An extension of prototypal inheritance
advantage:
- none
shortcoming:
- Just applies to objects, nothing to do with constructor inheritance
- Like borrowing the constructor pattern, function reuse cannot be achieved, and methods are created every time an object is created
Parasitic Compositional Inheritance
In the combined inheritance of prototype chain + borrowed constructor, the biggest disadvantage is that the parent class constructor will be called twice. If this problem is solved, will it become the best inheritance?
Instead of using joestarFamily.prototype = new Person()
, let JoestarFamily.prototype access Person.prototype with explicit prototypal inheritance
function Person(brain) {
this.brain = brain;
this.others = {
other1: 1,
other2: 2
};
this.setBrain = function () {
console.log("set brain");
}
}
Person.prototype.getBrain = function () {
console.log(this.brain)
}
Person.prototype.age = 100;
Person.prototype.like = {
color: 'red',
}
function JoestarFamily(name) {
this.name = name
this.sayName = function() {
console.log(this.name)
}
Person.call(this, "smart")
}
var F = function () {} // 核心代码
F.prototype = Person.prototype // 核心代码
JoestarFamily.prototype = new F()
JoestarFamily.prototype.constructor = JoestarFamily; // 原型的 constructor 指回原来的构造函数
JoestarFamily.prototype.sayHello = function() {}
var johnny = new JoestarFamily('johnny')
console.log(johnny)
Its prototype chain diagram is the same as composition inheritance:
The difference is that it lacks the built-in properties and methods of Person constructors such as brain, others, setBrain, etc. generated by new Person
on the prototype of JoestarFamily
The secret to its implementation lies in these few lines of code
var F = function () {} // 创建一个空构造函数
F.prototype = Person.prototype // 将Person原型赋值给空构造函数的原型
// 即 F.prototype 拥有了 Person.prototype 上所有的属性和方法,包括 constructor,getBrain,age,like,__proto__
JoestarFamily.prototype = new F()
// new F,等于JoestarFamily.prototype.__proto__ === F.prototype
This method refers to the core code of Object.create. Its essence is not to use the characteristics of new, but to use the method of explicit prototype inheritance, so that there is no need for side effects due to the use of new.
There is more than one type of explicit prototypal inheritance. What you can do with Object.create, I can also do with Object.setPrototypeOf
Object.setPrototypeOf(JoestarFamily.prototype, Person.prototype)
- var F = function () {}
- F.prototype = Person.prototype
- JoestarFamily.prototype = new F()
Note that no, new will have side effects, it will not only establish a prototype chain relationship, but also execute the code in the constructor, assign it to an object generated in memory, and return it to become an instance
And like explicit prototypal inheritance, it only links the relationship (prototype chain), which is relatively pure
Advantages: Same as composition inheritance
Disadvantages: none
class inheritance
In addition to the above types of inheritance, ES6 class inheritance is a syntactic sugar for simulating class inheritance, and its underlying implementation is still based on prototype
class Person {
constructor(brain) {
this.brain = brain;
this.others = {
other1: 1,
other2: 2
};
this.setBrain = function () {
console.log("set brain");
}
}
getBrain() {
console.log(this.brain)
}
age = 100
like = {
color: 'red'
}
}
class JoestarFamily extends Person{
constructor(name) {
super('smart')
this.name = name
this.sayName = function() {
console.log(this.name)
}
}
sayHello() {}
}
var johnny = new JoestarFamily('johnny')
console.log(johnny)
Prototype chain diagram:
The difference between class inheritance and traditional inheritance (compared with parasitic composition inheritance) is that
- The constructor also inherits:
JoestarFamily.__proto__ === Person
- The property inheritance of the parent class prototype object cannot be inherited, such as
Person.prototype.age
,Person.prototype.like
The responsibility of the class is to act as a template for creating objects. Generally speaking, the data data is carried by the instance, and the behavior/method is written in the class
That is to say, class-based inheritance, inherits behavior and structure, but does not inherit data
Prototype-based inheritance inherits data, structure and behavior
And why can't classes inherit data? This is to cater to the basic behavior of the class, it is deliberately blocked
The behavior/method can be shared and placed in the prototype object, while the data is unique and can be in the constructor, which can solve most scenarios, after all, other languages do this
case analysis
Come to two questions to practice legs
Implement the Person and Student objects as follows
- Student inherits Person
- Person contains an instance variable name and an instance method printName
- Student contains an instance variable score, contains an instance method printScore
- A method printCommon is shared between Person and Student
function Person(name) {
this.name = name
this.printName = function() {
console.log(this.name)
}
}
Person.prototype.commonMethods = function() {
console.log('共享方法')
}
function Student(name, score) {
this.score = score
this.printScore = function() {
console.log(this.score)
}
Person.call(this, name)
}
var F = function() {}
F.prototype = Person.prototype
Student.prototype = new F()
var johnny = new Person('johnny')
var elaine = new Student('elaine', 99)
console.log(johnny.commonMethods === elaine.commonMethods)
This question is relatively simple, another question, draw the prototype chain relationship diagram
class A {}
class B extends A {}
const b = new B();
If there are only a few prototype chain relationship diagrams, it is still simple:
But if you want to do it all
This involves the problem of Object and Function chicken-and-egg problem. For details, you can read this article to learn about one or two - the first emperor in JavaScript (updated in subsequent articles)
Summarize
According to what we said in the prototype , inheritance can be divided into explicit prototype inheritance and implicit prototype inheritance. Explicit prototype inheritance is Object.create, Object.setPrototypeOf, and implicit prototype inheritance is new, object literal.
In this section, we explain prototype chain inheritance, borrowed constructor inheritance, combined inheritance (prototype chain + borrowed constructor), prototypal inheritance (Object.create, Object.setPrototypeOf), parasitic inheritance, Parasitic combined inheritance (prototype + borrowed constructor), class inheritance and other inheritance methods
Also understand that prototypal inheritance is explicit prototypal inheritance
Prototype-related inheritance, such as: prototype chain inheritance, Object.create, Object.setPrototypeOf inheritance, has a common problem, that is, the properties and methods in the constructor cannot be inherited, and the reference properties of their prototype objects will be shared by the instance
The only solution is to borrow constructor inheritance, that is to call the this pointer in the subclass constructor, and both combined inheritance and parasitic combined inheritance can achieve a perfect prototype chain relationship. The difference between the two is that combined inheritance calls the construction twice function, the reason is because of the side effects of new, and the reason why parasitic composition inheritance can be better is that explicit prototypal inheritance does not produce side effects, only simple prototypal relationship associations
Although the knowledge point of prototypes is now less important in the front end, the reason is very simple, various frameworks of JavaScript began to abandon the mixin pattern and turned to the composition pattern (extracting methods into independent classes and auxiliary objects, and then put them compose, but do not use inheritance). The "composition over inheritance" design pattern has been followed by a lot of people, it's normal to not understand the prototype, and the operation of the composition pattern has also made functional programming popular, but that's another story
References
- JS: Deep Understanding of JavaScript - Inheritance
- Deep understanding of JavaScript prototypes
- How to Answer JavaScript Prototype Chain Questions in Interviews
series of articles
- Deep understanding of JavaScript - the beginning
- Deep understanding of JavaScript - what is JavaScript
- Deep understanding of JavaScript - what JavaScript consists of
- Deep understanding of JavaScript - everything is an object
- Deep understanding of JavaScript-Object (object)
- In-depth understanding of what JavaScript-new does
- Deep understanding of JavaScript-Object.create
- Deep understanding of JavaScript - the secret of copying
- Deep understanding of JavaScript - prototype
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。