Tamping foundation - hand tearing js inheritance

When it comes to JS inheritance, what do you think of first? Advantages and disadvantages of interview inheritance method, JS as a former sufferer, I read and forgot, forgot to see, read and forget, forget it, can't I give up treatment On the vast prairie, ten thousand grass mud horses are galloping. It's been 9012 years. The interviewer hasn't let me go.

ok, let's get down to business. Let's talk about the classic topic of js inheritance.

"Class" of JS

Unlike java, php and other traditional OOP languages, javascript does not have the concept of class itself, so how does it implement class simulation?

  1. Constructor mode
  2. Prototype mode
  3. Mixed mode

Constructor mode

Function Foo (name) {
    this.name = name
    this.like = function () {
        console.log(`like${this.name}`)
    }
}
let foo = new Foo('bibidong')

In this way, the class is defined through the constructor. In fact, it is the same as ordinary functions, but in order to distinguish from conventional functions, the function name is generally capitalized.

  • Disadvantages: methods of classes cannot be shared.

Prototype mode

function Foo (name) {}
Foo.prototype.color = 'red'
Foo.prototype.queue = [1,2,3]
let foo1 = new Foo()
let foo2 = new Foo()

foo1.queue.push(4)
console.log(foo1)   // [1, 2, 3, 4]
console.log(foo2)   // [1, 2, 3, 4]

We directly define the attributes and methods on the prototype object of the constructor through the prototype method, and the instances can share these attributes and methods, which solves the disadvantage of defining classes in the constructor method.

  • Disadvantages: we can see that we changed the data of foo1, and the queue attribute of foo2 also changed. This is the biggest problem of the prototype method. The attribute of reference type will be modified by other instances. In addition, parameters cannot be transferred in this way.

Mixed mode

function Foo (name) {   // The attribute is defined in the constructor
    this.name = name
    this.color = 'red'
    this.queue = [1,2,3]
}
Foo.prototype.like = function () {  // Methods are defined on prototypes
    console.log(`like${this.name}`)
}
let foo1 = new Foo()
let foo2 = new Foo()

The so-called mixed mode is to mix the above two methods. We define properties in the constructor and define methods to be shared on the prototype object, which can not only pass parameters, but also avoid the problem of the prototype mode.

To summarize: the ability of js class is simulated, which can be defined by constructor and prototype. The hybrid mode combines the advantages of the first two. In addition, there is object Create(), the class of ES6, can be used to create objects and define classes.

Common inheritance methods

1, Prototype chain inheritance

Based on the characteristics of prototype chain search, we take the instance of the parent class as the prototype of the child class. This inheritance method is prototype chain inheritance.

function Parent () {
    this.color = 'red'
    this.queue = [1,2,3]
}
Parent.prototype.like = function () {
    console.log('')
}

function Child () { }
Child.prototype = new Parent()  // The constructor pointer has changed to point to the Parent
Child.prototype.constructor = Child     // Manual repair

let child = new Child()

Child.prototype is equivalent to the instance of the Parent class. The instance property of the Parent class is hung on the prototype object of the subclass. Take the color property as an example, which is equivalent to this

Child.prototype.color = 'red'

In this way, the instance properties of the parent class are shared. When we print child, we can see that child does not have its own instance properties. It accesses its prototype object.

We create two instances child1 and child2

let child1 = new Child()
let child2 = new Child()
child1.color = 'bulr'
console.log(child1)
console.log(child2)

We modified the color attribute of child1, and child2 was not affected. It is not because other instances have independent color attributes, but because this color attribute is directly added to child1, and the color on its prototype does not move, so other instances will not be affected. This can be clearly seen from the printing results. What if the property we modify is a reference type?

child1.queue = [1,2,3,'I was modified'] // Reassign
child1.like = function () {console.log('like The method was modified by me')}
console.log(child1)
console.log(child2)

We have rewritten the queue attribute and like method of the reference type. In fact, it is exactly the same as modifying the color attribute. They are directly added to the instance attribute of child1. From the print results, we can see that these two attributes have been added to child1, while child2 will not be affected. Let's look at the following.

child1.queue.push('add push')   // There is no reassignment this time
console.log(child1)
console.log(child2)

If the re assignment is made, the address of the reference type will change, which has nothing to do with the prototype, so it will not affect the prototype. This time, we use the push method, which does not open up a new space for the attribute. The queue attribute of child2 has also changed. The queue attribute on the subclass Child prototype has been modified by the instance, which must affect all instances.

  • shortcoming

    • Instances of subclasses share properties of the type referenced by the constructor of the parent class
    • Cannot pass parameters when creating subclass instances

2, Constructor inheritance

It is equivalent to copying the instance attribute of the parent class to the subclass, which enhances the ability of the subclass constructor

function Parent (name) {
    this.name = name
    this.queue = [1,2,3]
}
Parent.prototype.like = function () {
    console.log(`like${this.name}`)
}

function Child (name) {
    Parent.call(this, name)    // Core code
}

let child = new Child(1)

We printed the child. We can see that the child class has the instance properties and methods of the parent class, but the child's__ proto__ There is no prototype object of parent class above. It solves two problems of prototype chain (each attribute of subclass instance is independent of each other and can pass parameters)

  • shortcoming

    • Subclasses cannot inherit the methods and properties above the parent prototype.
    • The method defined in the constructor will be created again every time an instance is created.

3, Combinatorial inheritance

People, like their names, must combine something. Yes, combined inheritance is the combination of the above two inheritance methods.

function Parent (name) {
    this.name = name
    this.queue = [1,2,3]
}
Parent.prototype.like = function () {
    console.log(`like${this.name}`)
}

function Child (name) {
    Parent.call(this, name)
}

Child.prototype = new Parent()
Child.prototype.constructor = Child     // Fix constructor pointer
let child = new Child('')

Next, let's do something to see if it can carry forward the advantages of prototype chain inheritance and constructor inheritance

let child1 = new Child('bibidong')
let child2 = new Child('huliena')
child1.queue.push('add push')
console.log(child1)
console.log(child2)

We updated the reference property of child1 and found that the instance of child2 was not affected, and the like method on the prototype was also. Yes, composite inheritance did carry forward the advantages of both and solve their shortcomings. In the combination mode, the instance attribute is usually defined on the constructor and the method to be shared is defined on the prototype object. The subclass inherits the method on the prototype of the parent constructor through the prototype chain inheritance method, and the subclass of the constructor inherits the instance attribute of the constructor through the constructor inheritance method. It is a perfect inheritance method in function.

  • Disadvantages: the constructor of the parent class is called twice. After the first call, the prototype of the Child class has the instance property of the parent class. In the second call, a copy of the instance property of the parent class is copied as the instance property of the Child class, and the property with the same name on the prototype of the Child class is overwritten. Although it is covered, there is no problem in function, but this redundant attribute with the same name always exists on the subclass prototype.
Child.prototype = new Parent() // Build prototype chain for the first time
Parent.call(this, name) // The second time, the new operator also executes the parent constructor through call

4, Prototype inheritance

Take an object as the basis and get a new object after processing. The new object will take the original object as the prototype. This inheritance method is prototype inheritance. In a word, the summary is to take the incoming object as the prototype of the new object to be created.

First write down this function with processing power

function prodObject (obj) {
    function F (){
        
    }
    F.prototype = obj
    return new F()  // Returns an instance object
}

This too Object.create()Implementation principle, so use Object.create Direct replacement prodObject Function is ok of
let base = {
    color: 'red',
    queue: [1, 2, 3]
}
let child1 = prodObject(base)
let child2 = prodObject(base)
console.log(child1)
console.log(child2)

Prototype inheritance is based on prototype, which is similar to prototype chain inheritance. In this way, the instance does not have its own attribute value and accesses the attributes on the prototype.

  • Disadvantages: same prototype chain inheritance

5, Parasitic inheritance

For the upgrading of prototype inheritance, parasitic inheritance encapsulates a function and internally enhances the objects generated by prototype inheritance.

function greaterObject (obj) {
    let clone = prodObject(obj)
    clone.queue = [1, 2, 3]
    clone.like = function () {}
    return clone
}
let parent = {
    name: 'bibidong',
    color: ['red', 'bule', 'black']
}
let child = greaterObject(parent)

After printing child, its shortcomings are also obvious. Parasitic inheritance enhances the object, but it can not avoid the problem of prototype chain inheritance.

  • shortcoming

    • Disadvantages of having prototype chain inheritance
    • In addition, internal functions cannot be reused

6, Parasitic combinatorial inheritance

Here comes the big move, and the parasitic combination comes on stage!

As mentioned above, the problem of composite inheritance is that it will call the secondary parent class, resulting in redundant attributes with the same name on the subclass prototype. Child.prototype = new Parent(), how should this line of code be modified?

Our goal is to make the instance attribute of the parent class not appear on the child class prototype prototype = Parent. Prototype. In this way, it can't guarantee that the subclass only mounts the methods on the parent prototype, and the instance properties are gone. The code is as follows. It seems that it's not too wonderful.

function Parent (name) {
    this.name = name
    this.queue = [1,2,3]
}
Parent.prototype.like = function () {
    console.log(`like${this.name}`)
}

function Child (name) {
    Parent.call(this, name)
}

Child.prototype = Parent.prototype // Just rewrite this line
Child.prototype.constructor = Child
let child = new Child('')

I suddenly found that the rewritten line if child If the prototype changes, doesn't it directly affect the parent class, for example, chestnuts

Child.prototype.addByChild = function () {}
Parent.prototype.hasOwnProperty('addByChild')   // true

The addByChild method is also added to the prototype of the Parent class, so this method is not elegant enough. It's the same line. Go directly to Parent There is a problem with prototype, so we can generate a new object with Parent as the prototype. Isn't this the processing function prodObject inherited by the prototype above

Child.prototype = Object.create(Parent.prototype) // Change to this

This solves all the problems. We're afraid to rewrite child Prototype affects the parent class through object The instance object returned by create, we will use child Prototype points indirectly to parent Prototype, when addByChild method is added, the attribute has nothing to do with the parent class.

Parasitic inheritance is also considered the most perfect combination.

summary

There are mainly six inheritance methods of js. The inheritance of es6 is a syntax sugar, and its essence is also based on parasitic combination. Among the six inheritance methods, prototype chain inheritance and constructor inheritance are the most basic and classic. Combinatorial inheritance aggregates their capabilities, and their functions are no problem. Prototype inheritance is similar to prototype chain. Parasitic inheritance changes from prototype inheritance, which enhances the ability of prototype inheritance. Finally, parasitic combinatorial inheritance solves the problem of combinatorial inheritance and is the most ideal way of inheritance.

Today's Tanabata, online begging, no girlfriend, just praise, slip away ~

Tags: Javascript Front-end

Posted by Angus on Fri, 20 May 2022 21:01:14 +0300