Deep understanding of javascript prototype chain and inheritance
Javascript itself is not an object-oriented language, but an object-based language. For those who are used to other OO languages, it is not suitable at first, because there is no "class" concept here, or the "class" and "instance" are not differentiated, let alone the "parent class" and "subclass. So what is the connection between this pile of objects in javascript?
Fortunately, javascript provided an implementation method of "inheritance" at the beginning of its design. Before learning about "inheritance", we should first understand the concept of prototype chain.
Prototype chain
We know that the prototype has a pointer to the constructor. What if we make the SubClass prototype object equal to the new SuperClass () of another type? In this case, the SubClass prototype object contains a pointer to the SuperClass prototype, and the SuperClass prototype also contains a pointer to the SuperClass constructor... In this way, a prototype chain is formed.
The Code is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Function SuperClass (){ This. name = "women" } SuperClass. prototype. sayWhat = function (){ Return this. name + ": I'm a girl! "; } Function SubClass (){ This. subname = "your sister "; } SubClass. prototype = new SuperClass (); SubClass. prototype. subSayWhat = function (){ Return this. subname + ": I'm a beautiful girl "; } Var sub = new SubClass (); Console. log (sub. sayWhat (); // women: I'm a girl! |
Use prototype chain to implement inheritance
From the code above, we can see that SubClass inherits the attributes and methods of SuperClass. This inheritance is implemented by assigning the SuperClass instance to the SubClass prototype object, in this way, the SubClass prototype object is overwritten by an instance of SuperClass, and has all its attributes and methods. It also has a pointer to the SuperClass prototype object.
When using prototype chain to implement inheritance, we need to pay attention to the following points:
Note the constructor changes after inheritance. The sub constructor points to the SuperClass because the SubClass prototype points to the SuperClass prototype. When understanding the prototype chain, do not ignore the default Object at the end, which is why we can use the built-in methods of toString and other objects in all objects.
When the prototype chain is used to implement inheritance, the prototype method cannot be defined literally, because this will overwrite the prototype object (which was also introduced in the previous article ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Function SuperClass (){ This. name = "women" } SuperClass. prototype. sayWhat = function (){ Return this. name + ": I'm a girl! "; } Function SubClass (){ This. subname = "your sister "; } SubClass. prototype = new SuperClass (); SubClass. prototype = {// The prototype object is overwritten because the SuperClass attributes and methods cannot be inherited. SubSayWhat: function (){ Return this. subname + ": I'm a beautiful girl "; } } Var sub = new SubClass (); Console. log (sub. sayWhat (); // TypeError: undefined is not a function |
Instance sharing issues. When we explained the prototype and constructor, we once introduced that the prototype containing the reference type property will be shared by all instances. Similarly, the inherited prototype also shares the property of the reference type in the "parent class" prototype. After we modify the reference type attribute of the "parent class" through the prototype inheritance, all other instances inherited from this prototype will be affected, which is not only a waste of resources, but also a phenomenon we do not want to see:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Function SuperClass (){ This. name = "women "; This. bra = ["a", "B"]; } Function SubClass (){ This. subname = "your sister "; } SubClass. prototype = new SuperClass (); Var sub1 = new SubClass (); Sub1.name = "man "; Sub1.bra. push ("c "); Console. log (sub1.name); // man Console. log (sub1.bra); // ["a", "B", "c"] Var sub2 = new SubClass (); Console. log (sub1.name); // woman Console. log (sub2.bra); // ["a", "B", "c"] |
Note: add an element to the array. All instances inherited from the SuperClass will be affected. However, modifying the name attribute will not affect other instances because the array is of reference type, name is the basic type.
How can we solve the problem of instance sharing? Let's take a look...
Constructor stealing)
Just as we have introduced that prototype definition objects are rarely used separately, prototype chain is rarely used in actual development. To solve the Sharing Problem of reference types, javascript developers introduce the classic inheritance mode (also known as borrow constructor inheritance). The implementation of this mode is very simple, that is, to call a super-Type constructor in a subtype constructor. We need to use the call () or apply () functions provided by javascript. Let's look at the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Function SuperClass (){ This. name = "women "; This. bra = ["a", "B"]; } Function SubClass (){ This. subname = "your sister "; // Assign the SuperClass scope to the current constructor for inheritance SuperClass. call (this ); } Var sub1 = new SubClass (); Sub1.bra. push ("c "); Console. log (sub1.bra); // ["a", "B", "c"] Var sub2 = new SubClass (); Console. log (sub2.bra); // ["a", "B"] |
SuperClass. call (this); this statement means that the initialization of the SuperClass constructor is called in the SubClass instance (context) environment, in this way, each instance will have its own copy of the bra attribute, which does not affect each other.
However, this implementation method is still not perfect. Since constructor is introduced, we are also faced with the constructor problems mentioned in the previous article: if there is a method definition in the constructor, there is a separate Function reference for none of the instances. Our goal is to share this method, in addition, the methods defined in the super-type prototype cannot be called in the sub-type instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Function SuperClass (){ This. name = "women "; This. bra = ["a", "B"]; } SuperClass. prototype. sayWhat = function (){ Console. log ("hello "); } Function SubClass (){ This. subname = "your sister "; SuperClass. call (this ); } Var sub1 = new SubClass (); Console. log (sub1.sayWhat (); // TypeError: undefined is not a function |
If you have read the previous article about prototype objects and constructor, you must already know the answer to this question. That is to follow the previous article's routine and use "Combination boxing "!
Combined inheritance
Combined inheritance is a combination of the advantages of the prototype chain and constructor to develop their respective strengths and combine them to implement inheritance. Simply put, the prototype chain is used to inherit attributes and methods, the constructor is used to inherit instance attributes. This not only solves the problem of instance attribute sharing, but also allows super-type attributes and methods to be inherited:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Function SuperClass (){ This. name = "women "; This. bra = ["a", "B"]; } SuperClass. prototype. sayWhat = function (){ Console. log ("hello "); } Function SubClass (){ This. subname = "your sister "; SuperClass. call (this); // second call of SuperClass } SubClass. prototype = new SuperClass (); // calls SuperClass for the first time Var sub1 = new SubClass (); Console. log (sub1.sayWhat (); // hello |
The combination inheritance method is also the most commonly used method for implementing inheritance in actual development. This can already meet your actual development needs, but there is no end to human pursuit of perfection, then, there will inevitably be people who are "nitpicking" this mode: You have called two super-type constructors in this mode! Twice... Are you sure you want to scale it up to one hundred times? How much is the performance loss?
The most powerful counterargument is to come up with a solution. Fortunately, developers have found the optimal solution to this problem:
Parasitic combined inheritance
Before introducing this inheritance method, we should first understand the concept of parasitic constructor. Parasitic constructor is similar to the factory model mentioned above. Its idea is to define a common function, this function is used to process object creation. After creation, this object is returned. This function is similar to a constructor, but the constructor does not return values:
1 2 3 4 5 6 7 8 9 10 11 12 |
Function Gf (name, bra ){ Var obj = new Object (); Obj. name = name; Obj. bra = bra; Obj. sayWhat = function (){ Console. log (this. name ); } Return obj; } Var gf1 = new Gf ("bingbing", "c ++ "); Console. log (gf1.sayWhat (); // bingbing |
The implementation of parasitic inheritance is similar to that of parasitic constructor. It creates a "Factory" function that does not depend on a specific type to process the inheritance process of objects and then returns the inherited object instance, fortunately, we don't need to implement this by ourselves. Brother Douglas has already provided us with an implementation method:
1 2 3 4 5 6 7 8 9 10 11 |
Function object (obj ){ Function F (){} F. prototype = obj; Return new F (); } Var superClass = { Name: "bingbing ", Bra: "c ++" } Var subClass = object (superClass ); Console. log (subClass. name); // bingbing |
A simple constructor is provided in a public function, and the instances of the passed objects are assigned to the prototype object of the constructor. The example of the constructor is returned, which is very simple but effective, isn't it? This method is called "original type inheritance" by future generations, and parasitic inheritance is implemented by enhancing the Custom Attributes of Objects Based on the original type:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Function buildObj (obj ){ Var o = object (obj ); O. sayWhat = function (){ Console. log ("hello "); } Return o; } Var superClass = { Name: "bingbing ", Bra: "c ++" } Var gf = buildObj (superClass ); Gf. sayWhat (); // hello |
The parasitic inheritance method is also faced with the problem of function reuse in the prototype. As a result, people began to build blocks again and came into being-parasitic combined inheritance, the purpose is to solve the problem of calling the parent Type constructor when specifying the child Type prototype, and at the same time, achieve maximum function reuse. The basic implementation method is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// The parameters are two constructors. Function inheritObj (sub, sup ){ // Implement instance inheritance to obtain a super-type copy Var proto = object (sup. prototype ); // Re-specify the constructor attribute of the proto instance Proto. constructor = sub; // Assign the created object to the sub-Type Prototype Sub. prototype = proto; } Function SuperClass (){ This. name = "women "; This. bra = ["a", "B"]; } SuperClass. prototype. sayWhat = function (){ Console. log ("hello "); } Function SubClass (){ This. subname = "your sister "; SuperClass. call (this ); } InheritObj (SubClass, SuperClass ); Var sub1 = new SubClass (); Console. log (sub1.sayWhat (); // hello |
This implementation avoids two super-type calls and saves SubClass. the prototype does not need any attributes, but also maintains the prototype chain. This really ends the inheritance journey, and this implementation method has become the most ideal inheritance implementation method! The controversy over the inheritance of javascript continues. Some people advocate OO, while others oppose the redundant efforts made in javascript to implement the features of OO. Let's take a look at it at least!