Native JS Column-Prototype/Prototype Chain, Five Inheritance Modes

Keywords: Attribute Java

Native js Column - Prototype/Prototype Chain, Inheritance Mode

Catalog:

  • prototype

  • Prototype Chain

  • inherit

    • Prototype Chain Inheritance

    • Borrow Constructor

    • Share prototypes

    • Holy Grail Mode

    • ES6 class

prototype

What about prototypes? We can think of them as ancestors. For us humans, our components are born with noses, eyes and cells, so we inherit some of the characteristics of our ancestors. Ancestors are also our prototypes. In programs, we can prototype objects when we create them so that they have some innate characteristics.This is the prototype in js. Let's look at the basic concepts of the prototype

Definition: A prototype is a method of a Function object that defines the common ancestor of an object constructed by a constructor. An object constructed by a constructor inherits the properties and methods of that common ancestor (prototype). A prototype is also an object. When a function is declared, it has an attribute called prototype, and prototype is the prototype.

Let's repeat this relationship. The prototype of the constructor is the father of the object that the constructor constructs (Dad and Ancestor are as close as you like), so take an example.

// The constructor Person, which was created when we added an attribute called prototype
// This prototype is the prototype, and as long as it is the object constructed by this constructor, their father is
// This prototype, so what's on this prototype, what's the object Person constructs
// If we do not set the prototype, the default is a {}
Person.prototype = {
    // The prototype defines a lastName value of curry, at which point all constructed by the Person constructor
    // Objects have lastName attributes, curry values, as if your last name had been predefined to follow your father's last name at birth
    lastName: 'Curry', 
}

Person.prototype.sayHi = function() {
    console.log('hello, Hello')
}
function Person() {};
    
var fstPerson = new Person();
var secPerson = new Person();
console.log(fstPerson.lastName); // No doubt, output curry
console.log(secPerson.lastName); // Output curry, since both sons are dads, they must all inherit this lastName
fstPerson.sayHi(); // Output hello, hello, because both methods and properties can inherit

Tips

If the constructor's own properties coincide with those on the prototype, then the relationship to the scope chain is consistent and close

// Dad's last name is Curry
Person.prototype.lastName = 'Curry';

function Person() {
    // Person is unhappy, just stand on his own door, don't follow his father's last name
    this.lastName = 'Kate';
}

var person = new Person();
console.log(person.name); // Kate, near 

Tips

We can use prototypes to extract common ancestors

For example, I now have a workshop specially for car production. The owner and color of the car can be selected, but the height and length brand is fixed.

function Car(owner, color) {
    this.owner = owner;
    this.color = color;
    this.lang = 4900;
    this.height = 1400;
    this.brand = 'BMW';
}

We know that the constructor new works by implicitly taking three steps inside

Var this = Object.create (prototype of this constructor);
Then execute the code you wrote, this.xxx = xxx;
Return this; //Finally return this 

Since this is the implicit three-step process, it means that even though I am in the Car shop lang, height, brand will not change, they will follow this. XXX = XXXX once every new time, which is not good, and the values of these attributes on each new object will be the same, it is a public attribute, which feels a little strange to create one side every time.So we use prototypes to extract these common attributes

Car.prototype = {
    lang: 4900,
    height: 1400,
    brand: 'BMW'
}
function Car(owner, color) {
    this.owner = owner;
    this.color = color; 
}

var fstCar = new Car('loki', 'black');
var secCar = new Car('thor', 'blue');
console.log(fstCar.lang); // 4900
console.log(secCar.brand); // BMW

A key

We've shown a lot of checks on prototype properties, so let's look at the addition and deletion of prototypes

  • Addition or deletion of prototype properties

    Constructor instances do not allow addition or deletion of any properties or methods on a prototype. If you want to add or delete a prototype, you must use a constructor to modify it

    Person.prototype = {
        lastName: 'Curry'
    }
    function Person() {}
    
    var person = new Person();
    console.log(person.lastName); // Be sure to output Curry 
    
    Person.prototype.lastName = 'Kate'; // Modify 
    var secPerson = new Person();
    console.log(secPerson.lastName); // Become Kate
    console.log(secPerson); // {}
    
    // What happens if we force a modification on an instance?
    
    Person.prototype = {
        lastName: 'Curry'
    }
    function Person() {}
    
    var person = new Person();
    console.log(person.lastName); // Be sure to output Curry 
    
    var secPerson = new Person();
    secPerson.lastName = 'Kate'
    console.log(secPerson.lastName); // Kate
    console.log(secPerson); // {lastName: 'Kate'}
    
    // We found that if you forcibly change the lastName of an instance, you add a lastName property to the instance. 
    // The prototype will not change and access will be close
    

    Additions, deletions and modifications are the same...

    Tips

    It's true that we can't modify the value of the constructor prototype in the instance, but it means that the address of the prototype property in the constructor can't be changed. If a property value in the prototype is an object, then we can operate. It doesn't matter how the data in the reference value changes as long as the address doesn't change.

    Person.prototype = {
        address: {
            province: 'Hunan'
        }
    }
    function Person() {}
    
    var person = new Person();
    
    console.log(person.address.province); // Hunan
    person.address.province = 'Guangdong';
    console.log(person.address.province); // Guangdong
    console.log(person); // {}
    

A key

Actually, this prototype does exist as soon as a function is created, and its initial value is similar to an empty object, but it is not an empty object. Two properties have been initialized on it, constructor and u proto_u. Take an example.

function Person() {}; 
console.log(Person.prototype); //Output {constructor: constructor:twin foo(), u proto_u: Object}

The output is as follows

That is to say prototype has been born with constructor and u proto_u attributes. Looking closely, we can see that these two attributes are light purple. Light purple attributes represent the built-in attributes of the system. So what are the two built-in attributes?

  • constructor

    Constructor, accessing this property returns the constructor that constructs the object, as follows

    function Person() {};
    
    var person = new Person();
    
    console.log(person.constructor); // ƒ Person() {}
    // Person's constructor is Person, so he returns Person
    
    

    The main function of this property is that we are developing a lot of constructors, there are many new instances, and we lost ourselves any day. If we don't know which instance was born, we will visit this constructor property to see the constructor of this instance.

    Tips

    The constructor attribute on the prototype can be modified, so it's okay not to fumble around. Instead, we'll make the constructor construct an instance that's wrong with who created it. For short, six parents won't recognize it, so we'll try not to modify the constructor's value.

    function Person() {};
    
    Person.prototype.constructor = 'hello? '
    
    var person = new Person();
    console.log(person.constructor); // Output hello?
        
    // A constructor is essentially an attribute on an object, so the attribute value can be any value
    
  • __proto__

The familiarity of u proto_ has only one effect: storing prototypes

Let's start with the u proto_u attribute on the instance

When we use the constructor new to present an instance, there is an implicit three-segment process inside, and the first step of the implicit three-segment process sets the prototype

Var this = Object.create (prototype of constructor)

The prototype of this constructor always has a place to put after the creation, so simply put the u proto_u attribute of the constructor instance into the prototype of the constructor

function Person() {};

var person = new Person();

console.log(person.__proto__ === Person.prototype); // Output true

A key

What's the use of this u proto_u property? When looking up an object for attributes it doesn't have, it will look up to its prototype through the u proto_u property. If it doesn't, it will continue to look up through its u proto_u property (his prototype is also an object, so there are u proto_u attributes) until it finds none at the top, it will return undefined.The layers of relationships that these u proto_ combine together are called prototype chains, and the way to find them is called access to prototype chains. First, let's talk about prototype chains, then go into details

var obj = {}; // Nothing on this obj

console.log(obj.toString); // I look for this property on obj to output twin toString () {[native code]}

Tips

This u proto_u property can also be changed manually, which can cause chaos in the prototype chain and is not recommended.

Person.prototype = {
    name: 'loki'
}

function Person() {};

var person = new Person();

console.log(person.name); // loki

person.__proto__ = {name: 'thor'};

console.log(person.name); // thor

Prototype Chain

OK, to the prototype chain, let's start with a chestnut

Person.prototype = {
    lastName: 'thor'
}
function Person() {}; 

var person = new Person();

console.log(person.name); // Output thor can't run away
console.log(person.constructor); // Output Person constructor can't run away either
console.log(person.toString); // ??? Output toString method

In the example above, the output person.name is because person. u proto_ points to Person.prototype, and the output constructor is because there is already a constructor in Person.prototype that points to the constructor Person itself. Why can the output toString output as well?

function Person() {};

var person = new Person();

console.log(person); // {__proto__}
console.log(Person.prototype); // {constructor: f Person() {}, __proto__};

We tried to output a person instance and Person.prototype, and found that there is also a u proto_u on Person.prototype. We said that u proto_ only works by storing prototype prototypes, that is, Person.prototype also has prototypes. And really, we opened Person.prototype. u proto_ and found the toString method inside.

A key

This invisible chain of objects connected by u proto_ above is called prototype chain

// The simple chain representation of the Person constructor above represents the relationship
person.__proto__  -> Person.prototype, Person.prototype.__proto__ -> Object.prototype

We can also write a prototype chain ourselves

// grandpa
Grand.prototype = {
    lastName: 'Curry'
}

console.log(Grand.prototype.__proto__ === Object.prototype); // true

function Grand() {}

// We want Father to connect Grand
Father.prototype = new Grand();
function Father() {}

// Let Son connect to Father
Son.prototype = new Father();
function Son() {}

var son = new Son();
console.log(son.lastName); // Output Curry

Tips

No special case, Object.prototype is the final prototype of all objects. The only special case is that Object.create, which is used to create objects. This method can accept a parameter to specify the prototype of the object being created. The prototype can be an object or null.

var obj = Object.create(null);
console.log(obj.toString); // Error obj.toString is not a function

Inheritance (Grail Mode)

In the long history of js, because he was not born with class es and extends like other static languages, the inheritance of JS has been bumpy. Several inheritance modes have appeared from the past to the present, which is also a long process of evolution and perfection.

  • Prototype Chain Inheritance

  • Inherit by borrowing a constructor

  • Share prototypes

  • Holy Grail Mode

  • Es6 class

I have a requirement that subclasses inherit the prototype properties of the parent class, and I compare the results in several different ways (borrowing a constructor to achieve inheritance is not really inheritance, it's deceased, so I'll give another example in this way).

  • Traditional Form=>Prototype Chain Inheritance

        Father.prototype = {
            lastName: 'Curry'
        }
        function Father() {
            this.age = 40;
        }
    
        var father = new Father();
    
        Son.prototype = father;
        function Son() {}
    
        var son = new Son();
    
        console.log(son.lastName); // Output Curry is undisputed
        console.log(son.age); // Output 40
    

    Actually, we can see that we don't want to inherit this age attribute, but we can't get rid of it. As long as we use the prototype chain inheritance method, this problem will occur and it will inherit too many useless attributes.

  • Inherit by borrowing a constructor

    The requirement is that I have a shop floor factory dedicated to producing cars, then another Porsche shop floor factory to produce Porsche, and then I somehow let the Porsche factory borrow the functions of the shop floor factory as follows

    function Car(lang, height) {
        this.lang = lang;
        this.height = height;
    }
    
    
    function Porsche(owner, color, lang, height) {
        // I don't have a way to control width and height in my Porsche shop. What can I do?
        // I ask someone else to borrow this to do my job. 
        Car.call(this, lang, height);
        this.owner = owner;
        this.color = color;
    }
    
    var porsche = new Porsche('loki', 'black', 4900, 1400);
    console.log(porsche);// Output {lang: 4900, height: 1400, owner:'loki', color:'black'}
    

    The above method of using calls and apply to implement my functions by borrowing other constructors is called borrowing constructor inheritance. The biggest shortcut to this method of inheritance is that you have to walk one more function at a time, which consumes a lot of performance, so few people will use it. Just know

  • Share prototypes

    It later came to mind that since I inherit too many attributes of the parent class through the prototype chain, my prototype does not point to an instance of the parent class, I point directly to the prototype of the parent class, and the child and parent share a prototype address, so don't I inherit too many attributes?

    Father.prototype = {
        lastName: 'Curry'
    }
    function Father() {
        this.age = 40;
    } 
    
    Son.prototype = Father.prototype; // We set the prototype address of the subclass directly to the prototype address of the parent class
    function Son() {}
    
    var son = new Son();
    console.log(son.lastName); // Output Curry
    console.log(son.age); // Output undefined
    
    // We'll find something strange, because the parent and child classes point to the same prototype address
    // Either one of the children or the parent modifies the prototype, and the other is affected.
    
    Son.prototype.habits = ['smoke', 'soccer']; // My son has some hobbies about smoking and playing football
    var father = new Father();
    console.log(father.habits); // ['smoke', 'soccer']
    
    

    Shared prototypes do not inherit too many other attributes of the parent class, but they also create a new problem because the parent and child prototypes point to the same address, so either parent or child class changes the constructor and the other is affected.

  • Holy Grail Mode

    Later, our pioneer predecessors developed the ultimate inheritance pattern, the Holy Grail pattern. He will not have the drawbacks of any of the above methods, but will also be used on a large scale in his work. Take a look at an example directly

    // The extracted public grail pattern method implements target inheritance from origin
    var grailInherit = (function() {
        var F = function() {}
        /*target: Target to inherit, origin: Target to be inherited*/
        return function(target, origin) {
            F.prototype = origin.prototype
            target.prototype = new F();
            target.constructor = target;
            target.superFather = origin;
        }
    } ())
    
    Father.prototype = {
        lastName: 'loki'
    }
    function Father() {
        this.age = 40;
    }
    
    function Son() {}
    
    grailInherit(Son, Father);
    
    Son.prototype.habits = ['smoke', 'soccer'];
    
    var father = new Father();
    var son = new Son();
    
    console.log(father.habits); // undefined
    console.log(son.lastName); // loki
    console.log(son.habits); // ['smoke', 'soccer']
    console.log(son.age); // undefined
    

    A key

    Using another constructor, F, we let F's prototype share Father's prototype directly, so that the new F instance doesn't inherit too many Father's attributes. Then we point Son.prototype at the F instance, so the F instance is clean and has no extra attributes of its own, so Son is clean and doesn't inherit too many attributes of F.At the same time, because the u proto_u of the F instance points to the prototype of the Father and the instance of Son u proto_ points to the F instance, the Son instance can read lastName on the Father, and any modification of the Son prototype has nothing to do with Father.

  • Inheritance of ES6

    In ES6, the official class and extends inheritance keywords have been introduced to help us achieve better inheritance. There are not many explanations here. They will be detailed later in the ES6 article, so you can mix them up first.

    class Father {
        constructor() {
            this.age = 40; // All values written in this.xxx = xxx code inside the constructor are written in the constructor
        }
        // All prototype properties and methods are written directly in class braces
        lastName = 'Curry'
    
        // All static attributes and methods need to be declared with the static keyword and written directly in class braces
        static isStatis = true
    }
    
    
    class Son extends Father {
        // As long as it is an inherited operation, super must be executed in the constructor, and the constructor used to execute the parent must be filled in. super is a keyword and does different things 
        constructor() {
            super();
        }
    }
    
    const son = new Son();
    
    console.log(son.lastName); // Curry
    console.log(son.age); // undefined
    

    Class is actually different from Java and C. The class class of js is essentially just the syntax sugar of the constructor. For the principle part, see ES6 column

Forty-nine original articles were published, 11 were praised, and 3466 were visited
Private letter follow

Posted by speps on Sun, 08 Mar 2020 19:50:57 -0700