preface

JavaScript is a language that is all objects except its basic types. It can be said that the core of JavaScript is objects. Therefore, it is very important to understand JavaScript objects and their various features. This article introduced my understanding of ES5 objects, stereotypes, stereotype chains, and inheritance

Note (this article is extremely long) that this article is just my personal understanding of JavaScript objects, not a tutorial. This article was written after I had just learned about JS objects. There must be some mistakes in this article, please kindly point them out by commenting below

What are JavaScript objects

var person = {   // Person is an object. Objects have various attributes, and each attribute has its own value
    // Key-value pair form
    name: "Mofan".// Can contain strings
    age: 20./ / digital
    parents: [  / / array
        "Daddy"."Mami",
    ]
    sayName: function(){  / / function
        console.log(this.name);
    },
    features: {   / / object
        height: "178cm".weight: "60kg",}}Copy the code

Everything except basic types in js is an object:

  • Function sayName(){} — sayName is the function object

  • Var arr = new Array() — arr is an Array object

Why is JavaScript designed this way? I think first of all, it’s unifying the data structure, making JavaScript a scripting language with a very liberal programming style: var whatever variable is defined; Second, JavaScript objects have properties and methods, and arrays of functions are objects, making calling references very flexible and convenient. Again, to build a prototype chain?

Several ways to create objects

  • The Object() pattern uses Object literals: var obj={… } as above or use the native constructor Object() :

    var person = new Object(a); person.name ="Mofan";
    person.sayName = function(){
        console.log(this.name);
        };
    console.log(person.name);//Mofan
    obj.sayName();//Mofan
    Copy the code
  • Mock classes using custom constructor patterns using function scopes (constructor patterns) :

    function Person(name,age){
        this.name = name;
        this.age = age;
        this.print = function(){
            console.log(this.name + this.age)
            };
    }
    var person = new Person("Mofan".19);
    console.log(person.name+person.age);//Mofan19
    person.print();//Mofan19Copy the code
  • Prototype mode:

    function Person(){}
    // You can write it like this
    /*Person.prototype.name = "Mofan"; Person.prototype.age = 19; Person.prototype.print = function(){ console.log(this.name+this.age); } * /
    // The following is recommended, but the two methods should not be mixed! Because the following way is actually rewritten
    // The Person prototype object. If the two are mixed, the latter assignment overrides the previous one
    Person.prototype = {
        name:"Mofan".age:19.print:function(){
            console.log(this.name+this.age); }}var person = new Person();
    console.log(person.name+person.age);//Mofan19
    person.print();//Mofan19Copy the code
  • Combined constructor pattern and stereotype pattern:

    function Person(name,age){
        // This is where the attributes are initialized
        this.name = name;
        this.age = age; . } Person.prototype = {// Public methods are defined here
        print:function(){
            console.log(this.name+this.age); },... }var person = new Person("Mofan".19);
    console.log(person.name+person.age);//Mofan19
    person.print();//Mofan19Copy the code
  • Dynamic prototyping mode:

    function Person(name,age){
        // Initialize the property
        this.name = name;
        this.age = age;
        // All public methods are defined when the first object is created (the first time it is called) and will not be called again
        if(typeof this.print ! ="function"){
            Person.prototype.print =function(){
                    console.log(this.name+this.age);
                };
            Person.prototype.introduction=function(){
                    console.log("Hi! I'm "+this.name+",I'm "+this.age);
                };
                // If you add methods to the prototype using object literals, the object created for the first time will not have those methods
            };
            
        
    }
    var person = new Person("Mofan".19);
    person.print();//Mofan19
    person.introduction();//Hi! I'm Mofan,I'm 19Copy the code

Other patterns use fewer scenarios

Application scenarios for these patterns

How can there be so many creation modes? In fact, the JS language is so flexible, so the predecessors concluded that there are several ways to create for different scenarios, each of which has its own advantages and disadvantages.

  • The first way, using literals or using the constructor Object(), is often used to create ordinary objects to store data and so on. Their prototypes are all Object and have no relation to each other. In fact, the following is the same:

    var o1 = {};// The literal representation
    var o2 = new Object;
    var o3 = new Object(a);var o4 = new Object(null);
    var o5 = new Object(undefined);
    var o6 = Object.create(Object.prototype);Var o = {}; // Create an Object based on the Object. Prototype templateCopy the code

  • The second way is to use function scope to mimic classes, so that you can refer to them when creating objects. You can create objects with different properties and implement object customization. The print method is also defined in the constructor, so if we were to use it as a public method, we would have this method for every new object, which would be a waste of memory. The constructor schema can be modified as follows:

    // Constructor method 2
    function print(){      // Define a global Function object that takes out the public methods
         console.log(this.name + this.age);
    }
    
    function Person(name,age){
        this.name = name;
        this.age = age;
   
        this.print = print.bind(this);// Each Person object shares the same print method version (the method has its own scope, so don't worry about variables being shared)
    }
    var person = new Person("Mofan".19);
    console.log(person.name+person.age);//Mofan19
    person.print();//Mofan19
    Copy the code

This, however, looks messy and doesn’t encapsulate classes. Use a prototype

  • The third way, pure prototype mode, is to add both properties and methods to the prototype. This has the advantage of saving memory, but the scope of application is less. More properties inside the object need to be customized, and once the prototype is changed, all the prototype instances will be changed. Therefore, we can combine the constructor approach to achieve object customization, which leads to a fourth approach — the combination of constructor and stereotype patterns, which can be customized in the constructor, and common in the stereotype, which also conforms to the characteristics of the constructor and stereotype. “This is the most widely used and recognized method of creating custom types in ES5” – JavaScript Advanced Programming, 3rd edition

  • The fifth approach, the dynamic prototype pattern, arises because some object-oriented developers, accustomed to class constructors, are confused and uncomfortable with the separate constructors and stereotypes. As a result, the dynamic prototype pattern appears, with the definition stereotype also written into the constructor. In dynamic prototype model about “if the object literal to prototype method, the addition of objects created for the first time will not have these methods” that is because before the if statement is executed, the first object has been created, and then execute the if statement inside, if use object literal to prototype assignment, This will result in the stereotype being rewritten after the instance is created, and the first instance created will lose the link to the stereotype, and there will be no methods in the stereotype. However, objects created later can use the methods in the prototype because they are created after the prototype has been modified.

What is the prototype

In JavaScript, a prototype is just an object, and there is no need to treat a prototype differently than any other object, except that it allows you to inherit properties from one object to another. Any object can also be a prototype. The reason why we often talk about the prototype of an object is actually to find the object from which the object inherits. An object is called a prototype relative to a prototype, that is, an object that calls an inherited object a prototype can itself be called a prototype of an object next in the prototype chain.

The [[Prototype]] property inside an object is created from birth and refers to the inherited object, called the Prototype. The prototype property inside the function object is also created naturally (only the function object has the Prototype property) and points to the function’s prototype object (not the function’s prototype!). . Var instance = new Class(); This way, every time a new function (which is used as a constructor) creates an instance, JavaScript assigns a reference to that Prototype to the instance’s Prototype property, so the [[Prototype]] property inside the instance points to the function’s Prototype object, the Prototype property.

A Prototype really refers to the [[Prototype]] property inside an object, not the Prototype property inside a function object. There is no relation between the two! For the [[Prototype]] property inside an object, different browsers have different implementations:

     var a = {}; 
 
     //Firefox 3.6+ and Chrome 5+ 
     Object.getPrototypeOf(a); //[object Object]   
     
     //Firefox 3.6+, Chrome 5+ and Safari 4+ 
    a.__proto__; //[object Object]   
     
     //all browsers 
     a.constructor.prototype; //[object Object]Copy the code

The reason a function object has a prototype property inside it, and you can use that property to create a prototype, is that JavaScript assigns a reference to that prototype to the instance’s prototype property every time a new instance of such a function is created (the function is used as a constructor). Methods and so on defined in the stereotype are shared by all instances, and once a property in the stereotype is defined, it is inherited by all instances (as in the example above). The implications of this in terms of performance and maintenance are self-evident. That’s what constructors are for. (JavaScript doesn’t define constructors, let alone distinguish between constructors and normal functions. Here are some examples:

    var a = {}    // An ordinary object
    function fun(){}   // An ordinary function
    // Normal objects have no prototype attribute
    console.log(a.prototype);//undefined
    console.log(a.__proto__===Object.prototype);//true
    
    // Only function objects have the prototype attribute
    console.log(fun.prototype);//Object
    console.log(fun.__proto__===Function.prototype);//trueconsole.log(fun.prototype.__proto__===Object.prototype);//true
    console.log(fun.__proto__.__proto__===Object.prototype);//true
    console.log(Function.prototype.__proto__===Object.prototype);//true
    console.log(Object.prototype.__proto__);//nullCopy the code

When you performconsole.log(fun.prototype);The output isAs you can see, every time a function is created, a Prototype property is created that points to the function’s prototype object (not the function’s prototype) and that prototype object automatically gets the constructor property, which is a pointer to the function that the Prototype property belongs to. while__proto__Properties are common to every object.

Then look at the top:

    function Person(){}// the constructor is capitalized
    var person1 = new Person();Person1 is an instance of Personconsole.log(person1.prototype);//undefined
    console.log(person1.__proto__===Person.prototype);//true
    console.log(person1.__proto__.__proto__===Object.prototype);//true
    console.log(person1.constructor);//function Person(){}
    
    The Person Function is an instance of the Function constructor
    console.log(Person.__proto__===Function.prototype);//true
    The prototype Object for Person is an instance of the constructor Object
    console.log(Person.prototype.__proto__===Object.prototype);//trueCopy the code

Person1 differs from the ordinary object a above in that it is an instance of the constructor Person. As mentioned earlier:

Var instance = new Class(); This way, every time a new function (which is used as a constructor) creates an instance, JavaScript assigns a reference to that Prototype to the instance’s Prototype property, so the [[Prototype]] property inside the instance points to the function’s Prototype object, the Prototype property.

So the [[Prototype]] property inside person1 points to the Prototype Object of Person, and the [[Prototype]] property inside the Prototype Object of Person points to Object. Prototype, which adds an Object to the Prototype chain. By doing this, Person1 has the method in the constructor’s prototype object.

In addition, the above code console.log(person1.constructor); //function Person(){} has no constructor attribute inside person1, which is found in person1.__proto__ by following the prototype chain.

This diagram illustrates the relationship between prototypes, constructors, and instances:

inheritance

JavaScript does not inherit this existing mechanism, but can emulate it with functions, stereotypes, and prototype chains. Here are three ways to inherit:

Class type inheritance

    / / parent class
    function SuperClass(){
        this.superValue = "super";
    }
    SuperClass.prototype.getSuperValue = function(){
        return this.superValue;
​
    };
    / / subclass
    function SubClass(){
        this.subValue = "sub";
    }
    // Class inheritance assigns a parent class instance to a subclass stereotype. The subclass stereotype and subclass instance have access to properties and methods copied from the parent stereotype and from the parent constructor
    SubClass.prototype = new SuperClass();
    // Add methods for subclasses
    SubClass.prototype.getSubValue = function(){
        return this.subValue;
    }
    
    / / use
    var instance = new SubClass();
    console.log(instance.getSuperValue);//super
    console.log(instance.getSubValue);//subCopy the code

This method of inheritance has two obvious disadvantages:

  • Cannot pass a parameter to the parent constructor while instantiating a child class

  • If a common attribute in a parent class has a reference type, it will be shared by all instances in the subclass. If an instance of any subclass changes the reference type, it will affect other subclass instances. Constructor inheritance can be used to solve this problem

Constructor inheritance

     / / parent class
    function SuperClass(id){
        this.superValue = ["big"."large"];// Reference type
        this.id = id;
    }
    SuperClass.prototype.getSuperValue = function(){
        return this.superValue;
​
    };
    / / subclass
    function SubClass(id){
        SuperClass.call(this,id);// Call the parent constructor and pass the argument
        this.subValue = "sub";
    }
     var instance1 = new SubClass(10);// Can pass arguments to the parent class
     var instance2 = new SubClass(11);
     
    instance1.superValue.push("super");
    console.log(instance1.superValue);//["big", "large", "super"]
    console.log(instance1.id);/ / 10
    console.log(instance2.superValue); ["big"."large"]
    console.log(instance2.id);/ / 11
    console.log(instance1.getSuperValue());//errorCopy the code

This approach addresses the drawbacks of class inheritance, but as you can see in the last line of code, there is no parent class prototype involved, so it violates code reuse. So combine them:

Combination of inheritance

     function SuperClass(id){
        this.superValue = ["big"."large"];// Reference type
        this.id = id;
    }
    SuperClass.prototype.getSuperValue = function(){
        return this.superValue;
​
    };
    / / subclass
    function SubClass(id,subValue){
        SuperClass.call(this,id);// Call the parent constructor and pass the argument
        this.subValue = subValue;
    }
     SubClass.prototype = new SuperClass();
      SubClass.prototype.getSubValue = function(){
        return this.subValue;
    }
    
     var instance1 = new SubClass(10."sub");// Can pass arguments to the parent class
     var instance2 = new SubClass(11."sub-sub");
​
    instance1.superValue.push("super");
    console.log(instance1.superValue);//["big", "large", "super"]
    console.log(instance1.id);/ / 10
    console.log(instance2.superValue); ["big"."large"]
    console.log(instance2.id);/ / 11
    console.log(instance1.getSuperValue()); ["big"."large"."super"]
    console.log(instance1.getSubValue());//sub
    console.log(instance2.getSuperValue());//["big", "large"]
    console.log(instance2.getSubValue());//sub-subCopy the code

Well, it’s pretty good, except that the superclass constructor is called twice, which causes the second call to create the instance to override the stereotype properties, and both the stereotype and the instance have those properties, which obviously doesn’t perform very well. Let’s start with Crockford’s parasitic inheritance:

    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
   }
    function createAnnther(original){
        var clone = object(original);
        clone.sayName = function(){
            console.log(this.name);
        }
        return clone;
   }
    var person = {
        name:"Mofan".friends: ["xiaoM"."Alice"."Neo"]};var anotherPerson = createAnnther(person);
    anotherPerson.sayName();//"Mofan"
}Copy the code

The idea is to make an existing object into a prototype of a new object and then reinforce it in createAnother. As you can see, Person is just a normal object, so this parasitic inheritance is suitable for creating an enhanced version of an existing object, which is really handy when the primary consideration is inheritance from existing objects rather than constructors. The drawback is that createAnother cannot be reused, and I have to write another function if I want to define other methods for another newly created object. If you look closely, the parasitic pattern is actually giving the prototype to a new object, which then strengthens it.

Wait, I’m a little confused about this, so let’s go back to where we started: What’s the purpose of inheritance? What should I inherit from the parent class? I think depending on what we want from the parent, I want all the common attributes of the parent (in the prototype) and the private attributes of the parent (in the constructor) that can be inherited by custom! The disadvantages of the previous models are mainly due to this:

    SubClass.prototype = new SuperClass();Copy the code

So why write this sentence? Do you just want to inherit a stereotype from a parent class? If so, why not:

    SubClass.prototype = SuperClass.prototype;Copy the code

The SuperClass. Prototype property is a pointer to the SuperClass prototype. If you assign this pointer to the prototype property of a subclass, then subclass Prototype will also point to the SuperClass prototype. Any change to subclass. prototype is a change to the superclass prototype, which is obviously not acceptable.

Parasitic combinatorial inheritance

If the object() function takes the parent as an argument, it returns an object that inherits from the parent, without calling the parent constructor or affecting the parent, which is perfect.

    function object(o){
        function F(){};
        F.prototype = o;
        return new F();
   }
    function inheritPrototype(subType,superType){
        var proto = object(superType.prototype);
        proto.constructor = subType;// modify the construcor attributes
        subType.prototype = proto;
   }
​
   function SuperClass(id){
        this.superValue = ["big"."large"];// Reference type
        this.id = id;
    }
    SuperClass.prototype.getSuperValue = function(){
        return this.superValue;
​
    };
    / / subclass
    function SubClass(id,subValue){
        SuperClass.call(this,id);// Call the parent constructor and pass the argument
        this.subValue = subValue;
    }
   inheritPrototype(SubClass,SuperClass);// Inherits the parent stereotype
    SubClass.prototype.getSubValue = function(){
        return this.subValue;
    }
    var instance1 = new SubClass(10."sub");// Can pass arguments to the parent class
     var instance2 = new SubClass(11."sub-sub");
​
    instance1.superValue.push("super");
    console.log(instance1.superValue);//["big", "large", "super"]
    console.log(instance1.id);/ / 10
    console.log(instance2.superValue);//["big", "large"]
    console.log(instance2.id);/ / 11
    console.log(instance1.getSuperValue());//["big", "large", "super"]
    console.log(instance1.getSubValue());//sub
    console.log(instance2.getSuperValue());//["big", "large"]
    console.log(instance2.getSubValue());//sub-subCopy the code

The parent class constructor is called only once, and the prototype chain remains the same.

    console.log(SubClass.prototype.__proto__===SuperClass.prototype);//ture
    console.log(SubClass.prototype.hasOwnProperty("getSuperValue"));//falseCopy the code

Therefore, this is the ideal inheritance for reference types.

conclusion

The ideal way to create objects for inheritance is to combine the constructor pattern with the stereotype pattern (or dynamic stereotype pattern), with definable private attributes in the constructor and common attributes in the stereotype. The ideal approach to inheritance is parasitic composition, in which the [[prototype]] attribute of a subclass points to the parent stereotype and then calls the parent constructor in the subclass constructor to implement the custom inherited parent attribute.

JavaScript objects have always been a bit confusing to me, but I’ll keep exploring. Let me first record what I understand and encourage you. Please kindly point out any mistakes, and I would appreciate your criticism.

This article is the author’s original, please note the link to reprint, the author reserves the right.

Reference: [1] www.cnblogs.com/chuaWeb/p/5… [2] www.cnblogs.com/xjser/p/496… [3] javascriptweblog.wordpress.com/2010/06/07/…