2

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 PersonJoestarFamily.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
  • 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)

Object.create继承

The relationship of the prototype chain is as follows:

Object.create继承原型链关系

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)

Object.setPrototypeOf继承

Its prototype chain relationship diagram:

Object.setPrototypeOf继承原型链关系

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

series of articles


山头人汉波
391 声望554 粉丝