In the previous article, the concept of prototype was introduced and the relationship between the three good friends of constructor, prototype object, and instance in JavaScript: each constructor has a "guardian" - the prototype object, and there is also a "position" of the constructor in the heart of the prototype object. The two are in love, but the instance is "secretly in love" with the prototype object, and she also keeps the position of the prototype object in her heart.
JavaScript itself is not an object-oriented language, but an object-based language. For those who are used to other OO languages, it is a bit uncomfortable at first because there is no concept of "class" here, or there is no distinction between "class" and "instance", let alone the difference between "parent class" and "subclass". So, how do these piles of objects in javascript be linked in this way?
Fortunately, JavaScript provided an implementation method of "inheritance" at the beginning of its design. Before understanding "inheritance", we will now understand the concept of prototype chains.
Prototype chain
We know that prototypes have a pointer to the constructor. What if we make the SubClass prototype object equal to another instance of the new SuperClass()? At this time, 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 specific code is as follows:
function SuperClass(){ this.name = "women" } SuperClass.prototype.sayWhat = function(){ return this.name + ":i`ma girl!"; } function SubClass(){ this.subname = "your sister"; } SubClass.prototype = new SuperClass(); SubClass.prototype.subSayWhat = function(){ return this.subname + ":i`ma beautiful girl"; } var sub = new SubClass(); console.log(sub.sayWhat());//women:i`ma girl!Use prototype chain to achieve inheritance
From the above code, we can see that SubClass inherits the properties and methods of SuperClass. This inherited implementation is by assigning the instance of SuperClass to the prototype object of SubClass. In this way, the prototype object of SubClass is overwritten by an instance of SuperClass, having all its properties and methods, and also having a pointer to the prototype object of SuperClass.
There are some things that need to be paid attention to when implementing inheritance using prototype chains:
Pay attention to the changes in the constructor after inheritance. Here the constructor of sub points to SuperClass because the prototype of SubClass points to SuperClass. When understanding the prototype chain, don't ignore the default Object object at the end, which is why we can use built-in methods such as toString in all objects.
When implementing inheritance through the prototype chain, you cannot use literal definition of the prototype method, because this will rewrite the prototype object (also introduced in the previous article):
function SuperClass(){ this.name = "women" } SuperClass.prototype.sayWhat = function(){ return this.name + ":i`ma girl!"; } function SubClass(){ this.subname = "your sister"; } SubClass.prototype = new SuperClass(); SubClass.prototype = {//The prototype object is overwritten here because the SuperClass attributes and methods cannot be inherited subSayWhat:function(){ return this.subname + ":i`ma beautiful girl"; } } var sub = new SubClass(); console.log(sub.sayWhat());//TypeError: undefined is not a functionAn issue with instance sharing. When explaining the prototype and constructor earlier, we once introduced that prototypes containing reference type attributes will be shared by all instances. Similarly, the properties of reference type in the "parent class" prototype will also be shared in the prototype. When we modify the reference type attributes of the "parent class" through the prototype inheritance, all other instances inherited from the prototype will be affected. This not only wastes resources, but also a phenomenon that we do not want to see:
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 here, all instances inherited from SuperClass will be affected, but if you modify the name attribute, it will not affect other instances, because the array is a reference type and name is a primitive type.
How to solve the problem of instance sharing? Let's continue to look down...
Classic inheritance (constructor stealing)
As we have introduced that we rarely use prototypes to define objects alone, in actual development, we rarely use prototype chains alone. In order to solve the problem of sharing reference types, javascript developers have introduced the classic inheritance pattern (some people call borrowed constructor inheritance). Its implementation is very simple to call supertype constructors in subtype constructors. We need to use the call() or apply() function provided by javascript, let's take a look at the example:
function SuperClass() { this.name = "women"; this.bra = ["a", "b"];}function SubClass() { this.subname = "your sister"; //Assign the scope of SuperClass to the current constructor to implement the inheritance of 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 sentence means that the initialization work of the SuperClass constructor is called in the instance (context) environment of SubClass, so that each instance will have its own copy of the bra attribute, which will have no effect on each other.
However, this implementation method is still not perfect. Since the constructor is introduced, we also face the problem with the constructor mentioned in the previous article: if there is a method definition in the constructor, then there is a separate Function reference for none of the instances. Our purpose is to share this method, and the methods we define in the supertype prototype cannot be called in the subtype instance:
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 functionIf you have read the previous article about prototype objects and constructors, you must already know the answer to solving this problem, that is, follow the routine of the previous article and use "combination punch"!
Combination inheritance
Combination inheritance is a way to combine the advantages of the prototype chain and the constructor, and to combine them to achieve inheritance. Simply put, it is to use the prototype chain to inherit attributes and methods, and use borrowed constructors to implement the inheritance of instance attributes. This not only solves the problem of instance attribute sharing, but also allows super-type attributes and methods to be inherited:
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); //The second call to SuperClass } SubClass.prototype = new SuperClass(); //The first call to SuperClass var sub1 = new SubClass(); console.log(sub1.sayWhat()); //helloThe combination inheritance method is also the most commonly used way to implement inheritance in actual development. At this point, it can meet your actual development needs, but people's pursuit of perfection is endless, so there will inevitably be someone "find" about this pattern: your pattern has called the super-type constructor twice! Two times. . . Did you make it? Is this amplification of 100 times the performance loss?
The most powerful refutation is to come up with a solution, but fortunately the developer has found the best solution to this problem:
Parasitic combination inheritance
Before introducing this inheritance method, we first understand the concept of parasitic constructor. Parasitic constructor is similar to the factory pattern mentioned above. Its idea is to define a common function. This function is specifically used to handle the creation of an object. After the creation is completed, it returns this object. This function is very similar to a constructor, but the constructor does not return a value:
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());//bingbingThe implementation of parasitic inheritance is similar to the parasitic constructor. It creates a "factory" function that does not depend on specific types, specifically deals with the object inheritance process, and then returns the inherited object instance. Fortunately, this does not require us to implement it ourselves. Dao Ge (Douglas) has long provided us with an implementation method:
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);//bingbingA simple constructor is provided in a public function, and then an instance of the object passed in is assigned to the prototype object of the constructor, and finally returning an instance of the constructor is very simple, but the efficacy is very good, isn't it? This method is later called "prototype inheritance", and parasitic inheritance is achieved based on the prototype by enhancing the object's custom properties:
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();//helloParasitic inheritance also faces the problem of function reuse in prototypes, so people began to assemble building blocks again, and parasitic combination inheritance was born, with the purpose of solving the problem of calling the parent type constructor when specifying a subtype prototype, and at the same time, to maximize the reuse of the function. Based on the above basic implementation methods are as follows:
//The parameters are two constructors function inheritObj(sub,sup){ //Implement instance inheritance and obtain a copy of the supertype var proto = object(sup.prototype); //Respecify the constructor attribute of the proto instance proto.constructor = sub; //Assign the created object to the prototype of the subtype sub.prototype = proto;}function SuperClass() { this.name = "women"; this.bra = ["a", "b"];}SuperClass.prototype.sayWhat = function() { console.log("hello");}function SubClass() { 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()); //helloThis implementation avoids two calls to supertypes, and also saves unnecessary properties on SubClass.prototype, and also maintains the prototype chain. At this point, the inheritance journey is truly over, and this implementation has also become the most ideal inheritance implementation method! People's controversy over the inheritance of javascript is still continuing. Some advocate OO, and some oppose making unnecessary efforts in javascript to realize the characteristics of OO. What if it is, at least I have a deeper understanding!