1. The prototype chain

Prototype chain inheritance is implemented by creating an instance of the constructor and assigning it to the prototype of the subclass

function Person() { this.name = 'Jack'; this.age = 18; } Person.prototype.sayAge = function() { return this.age } function Person1 () {} Person1.prototype = new Person() // Inherit Person, create an instance of Person and assign it to the prototype of Person1 let instance1 = new Person1(); console.log(instance1.sayAge()) // 18Copy the code

The key to implementing inheritance in this example is that Person1 does not use the default stereotype, but instead replaces it with a new object. This new object happens to be an instance of Person. In this way, the Person1 instance not only inherits properties and methods from the Person1 instance, but also hooks into the Person1 stereotype.

Problems with prototype chains:

  1. The reference values contained in the stereotype are shared across all instances, which is why attributes are usually defined in constructors rather than stereotypes.
function Person() { this.hobby = ["basketball", "sing", "football"]; } function Person1() {} function Person1. Prototype = new Person(); let instance1 = new Person1(); instance1.hobby.push("sleep"); console.log(instance1.hobby); // "basketball", "sing", "football, sleep" let instance2 = new Person1(); console.log(instance2.hobby); // "basketball", "sing", "football, sleep"Copy the code

In this example, the Person constructor defines a Hobby property that contains an array (reference values). Each instance of Person has its own Hobby property, which contains its own array. However, when Person1 inherits Person from a prototype, person1.prototype becomes an instance of Person and thus gets its own hobby property. This is similar to creating the person1.prototype. hobby property. The end result is that all instances of Person1 share the Hobby property. This is reflected by changes to Instance1. hobby.

  1. The second problem with stereotype chains is that subtypes cannot take arguments to the parent type’s constructor when instantiated. In fact, we cannot pass arguments into the parent class’s constructor without affecting all object instances. This, combined with the problem of including reference values in the stereotypes mentioned earlier, results in the stereotype chain being rarely used alone.

2. Embezzle constructors

To solve inheritance problems caused by stereotypes containing reference values, a technique called constructor Constructor stealing has become popular in the development community (this technique is sometimes called object spoofing or classical inheritance). The idea is simple: call the superclass constructor in the subclass constructor. Because functions are, after all, simple objects that execute code in a particular context, you can use the apply() and call() methods to execute constructors in the context of the newly created object. See how the problem of sharing reference values through stereotype chain inheritance is solved in the following example:

function Person() { this.hobby = ["basketball", "sing", "football"]; } function Person1() {// Inherit Person person.call (this); } let instance1 = new Person1(); instance1.hobby.push("sleep"); console.log(instance1.hobby); // "basketball", "sing", "football, sleep" let instance2 = new Person1(); console.log(instance2.hobby); // "basketball", "sing", "football"Copy the code

1. Pass parameters

One advantage of using a stolen constructor over a prototype chain is that you can pass arguments to the superclass constructor in the subclass constructor. Consider the following example:

function Person(name){ this.name = name; } function Person1() {person.call (this, "Jack");} function Person1() {person.call (this, "Jack"); // Instance attribute this.age = 29; } let instance = new Person1(); console.log(instance.name); // "Jack"; console.log(instance.age); / / 29Copy the code

Problem with embezzling constructors

The main drawback of embezzling constructors is also the problem of using constructor patterns to customize types: methods must be defined in constructors, so functions cannot be reused. In addition, subclasses do not have access to methods defined on the parent class prototype, so all types can only use the constructor pattern. Because of these problems, the stolen constructor is basically not used alone.

3. Combinatorial inheritance

Combinatorial inheritance (sometimes called pseudo-classical inheritance) combines the best of both archetypal chains and stolen constructors. The basic idea is to use stereotype chains to inherit properties and methods on stereotypes, and to inherit instance properties by stealing constructors. This allows methods to be defined on prototypes for reuse and allows each instance to have its own attributes. Consider the following example:

function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); }; Function SubType(name, age){// Inherit supertype. call(this, name); this.age = age; } // subtype.prototype = new SuperType(); SubType.prototype.sayAge = function() { console.log(this.age); }; let instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); console.log(instance1.colors); // "red,blue,green,black" instance1.sayName(); // "Nicholas"; instance1.sayAge(); // 29 let instance2 = new SubType("Greg", 27); console.log(instance2.colors); // "red,blue,green" instance2.sayName(); // "Greg"; instance2.sayAge(); / / 27Copy the code

Problem with composite inheritance: parent constructor called twice (memory consumption)

4. Original type inheritance

        function object(o) {
            function F() {}
            F.prototype = o;
            return new F();
        }
Copy the code

The object() function creates a temporary constructor, assigns the passed object to the constructor’s prototype, and returns an instance of the temporary type. In essence, object() makes a shallow copy of the object passed in. Consider the following example:

        let person = {
            name: "Nicholas",
            friends: ["Shelby", "Court", "Van"]
        };
        let anotherPerson = object(person);
        anotherPerson.name = "Greg";
        anotherPerson.friends.push("Rob");
        let yetAnotherPerson = object(person);
        yetAnotherPerson.name = "Linda";
        yetAnotherPerson.friends.push("Barbie");
        console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
Copy the code

ECMAScript 5 normalizes the concept of type-inheritance by adding the object.create () method. This method takes two parameters: the object to be the prototype for the new object and, optionally, the object to define additional properties for the new object. With only one argument, object.create () has the same effect as the Object () method here:

        let person = {
            name: "Nicholas",
            friends: ["Shelby", "Court", "Van"]
        };
        let anotherPerson = Object.create(person);
        anotherPerson.name = "Greg";
        anotherPerson.friends.push("Rob");
        let yetAnotherPerson = Object.create(person);
        yetAnotherPerson.name = "Linda";
        yetAnotherPerson.friends.push("Barbie");
        console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
Copy the code

Parasitic inheritance

A closer cousin of the original form of inheritance was Parasitic inheritance, a model pioneered by Crockford. The idea behind parasitic inheritance is similar to the parasitic constructor and factory pattern: Create a function that implements inheritance, enhance an object in some way, and then return that object. The basic parasitic inheritance pattern is as follows:

function createAnother(original){ let clone = object(original); SayHi = function() {// Somehow enhance this object console.log("hi"); }; return clone; // Return this object}Copy the code

In this code, createAnother() takes an argument, which is the base object of the new object. The object Original is passed to the object() function, which assigns the returned new object to Clone. Add a new method sayHi() to the Clone object. Finally, this object is returned. The createAnother() function can be used like this:

        let person = {
            name: "Nicholas",
            friends: ["Shelby", "Court", "Van"]
        };
        let anotherPerson = createAnother(person);
        anotherPerson.sayHi(); // "hi"
Copy the code

6. Parasitic combination inheritance

Combinatorial inheritance also has efficiency problems. The main efficiency issue is that the superclass constructor is always called twice: once when the subclass stereotype is created and once in the subclass constructor. Essentially, the subclass stereotype ends up containing all of the instance properties of the superclass object, and the subclass constructor simply overrides its own stereotype at execution time. Take a look at this combinatorial inheritance example:

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); // Call SuperType() this.age = age; } SubType.prototype = new SuperType(); / / the first call SuperType () SubType. The prototype. The constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); };Copy the code

Parasitic combinatorial inheritance inherits attributes by stealing constructors, but uses a hybrid prototype chain inheritance approach. The basic idea is that instead of assigning a value to a subclass prototype by calling the superclass constructor, you get a copy of the superclass prototype. It boils down to using parasitic inheritance to inherit the parent prototype and then assigning the returned new object to the child prototype. The basic pattern of parasitic combinatorial inheritance is as follows:

function inheritPrototype(subType, superType) { let prototype = object(superType.prototype); Prototype. constructor = subType; // Add object subtype. prototype = prototype; // Assign object}Copy the code

The inheritPrototype() function implements the core logic of parasitic composite inheritance. This function takes two arguments: a subclass constructor and a superclass constructor. Inside this function, the first step is to create a copy of the superclass prototype. We then set the constructor property to the returned Prototype object, fixing the problem of losing the default constructor due to rewriting the prototype. Finally, the newly created object is assigned to the prototype of the subtype. Call inheritPrototype() to implement the subtype stereotype assignment in the previous example, as in the following example:

        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);
        };
Copy the code

The SuperType constructor is called only once, avoiding unnecessary and unnecessary attributes on subtype. prototype, so it can be said that this example is more efficient, and not assigning an instance of the parent class to the subclass’s prototype keeps the attributes on the subclass’s prototype from duplicating the attributes on the subclass’s instance. Moreover, the prototype chain remains unchanged, so the instanceof operator and isPrototypeOf() methods work properly. Parasitic combinatorial inheritance is the best model for reference type inheritance.