In a class based language, an object is an instance of a class, and a class can inherit from another class. JavaScript is a prototype based language, which means that objects inherit directly from other objects.
JavaScript provides a very rich set of code reuse patterns, and there are many ways to implement inheritance. Before understanding these methods, we must first have a certain understanding of the concept of "object"
object
-
_ _ proto _ _
In JS, everything is an object. And__ proto__ Is a property of every object.
__ proto__ Attributes refer to an object from an object. Its function is to access an object when its attribute does not exist__ proto__ Find the object pointed to by the property. If the property still does not exist, continue to search until the top is null. This forms a chain, the so-called prototype chain.
let a={name:'haha'}; let b={}; b.__proto__=a; console.log(b.name);//haha //Although b has no name attribute, it finds the name attribute of a through the prototype chain
-
How and how objects are generated__ proto__ Direction of
In js, there are many ways to generate objects. The proto points of objects generated by different methods are also different. The three most commonly used methods of generating objects and their proto points are given below
var a={}; var result = a.__proto__===Object.prototype; console.log(result);//true //The proto of an object created in literal form points to the prototype of the Object() function by default function A(){}; var a=new A(); var result=a.__proto__===A.prototype; console.log(result);//true //The proto of an object created by a constructor points to the prototype of the constructor var a1={} var a2=Object.create(a1); var result = a2.__proto__===a1; console.log(result);//true //The proto of an object can actually point to other objects freely
Examples and prototypes
After having a certain understanding of the 'object' in js, the next thing to understand is the concept of instance and prototype. Both ES5 and ES6 have the concepts of prototype attribute and instance attribute, but the expression of ES5 is more intuitive. Therefore, we first take ES5 as an example to briefly introduce what is prototype attribute and what is instance attribute
//Variables or methods defined in construction methods belong to instance properties function A() { this.x = 10; this.printX = function () { console.log(this.x); } } //The variables or methods defined by prototype belong to prototype properties, which are stored in prototype A.prototype.y = -10; A.prototype.printY = function () { console.log(this.y); } let a = new A(); //For instance properties, each created instance object independently opens up a memory space for storing variables and methods of instance properties console.log(Object.getOwnPropertyNames(a));//[ 'x', 'printX' ] //For prototype properties, each instance object created shares variables and methods of prototype properties console.log(Object.getOwnPropertyNames(A.prototype));//[ 'constructor', 'y', 'printY' ] a.printX();//10 a.printY();//-10 //Due to the shared nature of prototype properties, all instances will be affected when modifying prototype properties let aa = new A(); aa.__proto__.y = -20; a.printY();//-20
ES6 adds the concept of class, which blurs the boundary between instance and prototype, but this difference still exists
class A{ //The attribute set in the constructor is the instance attribute, which is similar to the construction method in ES5 constructor() { this.aaa = 'aaa'; } //Ordinary methods are regarded as prototype properties and will be stored in the prototype of class A foo() { console.log('foo'); } //In fact, the arrow function belongs to the instance attribute. All instances generated through class A will contain the arrow function bar = () => { } } class B extends A{ constructor() { super(); } } class C extends B{ constructor() { super(); } fun() { super.foo(); } } let a = new A(); let b = new B(); let c = new C(); //The custom prototype attribute in class A contains only foo console.log(Object.getOwnPropertyNames(A.prototype));//[ 'constructor', 'foo' ] //The instance generated by class A contains both the attribute aaa defined in the constructor and the arrow function bar console.log(Object.getOwnPropertyNames(a));//[ 'bar', 'aaa' ] //Although class B inherits from A, it should be noted that the prototype attribute in A is not given to B. if the instance of B needs to call the foo method, you need to find it one by one through the prototype chain console.log(Object.getOwnPropertyNames(B.prototype));//[ 'constructor' ] //Obviously, through inheritance, B obtains the instance attribute of A, which can be proved by its example B including bar and aaa console.log(Object.getOwnPropertyNames(b));//[ 'bar', 'aaa' ] //Inheritance does not affect the addition of new prototype properties to subclasses console.log(Object.getOwnPropertyNames(C.prototype));//[ 'constructor', 'fun' ] //Fun belongs to the prototype attribute of class C, so the instance of class C will not contain fun console.log(Object.getOwnPropertyNames(c));//[ 'bar', 'aaa' ] //Instance c does not contain the foo method, but it can still be called, that is, the foo method in class A is called through the prototype chain c.foo();//foo //In addition, instance c can achieve the same purpose by calling the fun method. This is because the super keyword is used in fun. The underlying principle is to find the foo method layer by layer through the prototype chain. Finally, the foo method is found in the prototype of class A c.fun();//foo //If we define the foo method as an arrow function, c.fun() will report an error, because the arrow function will be regarded as an instance attribute, and the path of the prototype chain search is the prototype attribute, which explains why the parent function called by super cannot be an arrow function
Arrow function
Here is a brief introduction to the arrow function, focusing on the difference between the arrow function and binding this
class A{ constructor() { this.x = 100; this.bar = this.bar.bind(this);//By binding this, the bar method is also left in the instance } foo() { } bar() { } temp = () => { } } let a = new A(); console.log(Object.getOwnPropertyNames(A.prototype));//[ 'constructor', 'foo', 'bar' ] console.log(Object.getOwnPropertyNames(a));//[ 'temp', 'x', 'bar' ]
In the above example, three methods are defined in class A, of which foo and bar are placed in the prototype attribute, and the arrow function temp is regarded as the instance attribute. Here, we bind this to the bar method. Through the log, we can find that the bar method, like temp, also exists in instance a. in other words, binding this enables the bar method to exist in prototype attributes and instance attributes at the same time
The following is a specific use environment for binding this
class A { constructor() { this.x = 100; // this.printX = this.printX.bind(this); } foo() { let temp = { x: 6, print: this.printX } temp.print(); } bar() { this.printX(); } printX() { console.log(this.x); } } let a = new A(); a.printX();// 100 a.bar();// 100 a.foo();// 6
As can be seen from the above example, when the foo method does not bind this, the direction of this will be inconsistent with what we expect. This problem can be solved by binding this or arrow function.
Implementation of inheritance
In fact, the above content has partially introduced inheritance. Here we focus on comparing the differences between ES5 and ES6 in inheritance use
- Differences between ES5 and ES6
ES6 uses a more understandable class to implement inheritance, but in fact, class is only the syntax sugar of the constructor, and class itself is also a function
class Person{}; let result = typeof Person ==='function'; console.log(result);//true
Implementation of inheritance in ES5 (one way: prototype chain inheritance)
function Father(){ this.name='father'; } function Child(){ this.id='001'; } Child.prototype=new Father(); let child=new Child(); console.log(child.name);//father console.log(child.id);//001
If the constructor is compared to a class, the Child class inherits by setting the prototype property to an instance of the Father class.
Implementation of inheritance in ES6
class Father{ constructor(){ this.name='father'; } } class Child extends Father{ constructor(){ super(); this.id='001'; } } let child=new Child(); console.log(child.name);//father console.log(child.id);//001
The essence of inheritance in ES5 is to first create the instance object this of the subclass, and then add the methods of the parent class to this (Parent.apply(this)). The inheritance mechanism of ES6 is completely different. The essence is to add the properties and methods of the parent instance object to this (so you must call the super method first), and then modify this with the constructor of the subclass.
- Several implementations of inheritance in ES5
Prototype chain inheritance (ditto)
function Father(name){ this.name=name; this.printName=function(){ console.log(this.name); } } Father.prototype.age=40; function Child(){ this.name='child'; } Child.prototype=new Father('father');//The prototype chain is established let result = Child.prototype.__proto__===Father.prototype; console.log(result);//true let child = new Child(); console.log(child);//{name: "child"} console.log(child.age);//40 console.log(child instanceof Father);//true console.log(child instanceof Child);//true //The instanceof operator is used to detect whether the prototype of the constructor exists on the prototype chain of the instance. console.log(child.__proto__);//{ name: 'father', printName: [Function] }
Because the prototype of the subclass passes in the instance of the parent class, the subclass instance inherits all the properties of the parent class (constructor properties and properties in the prototype). If the subclass instance child wants to add new properties or overwrite the original properties, it needs to be defined in the subclass constructor child.
Disadvantages: 1. The new instance cannot pass parameters to the parent constructor.
2. Single inheritance without multiple inheritance.
3. The properties on the prototype are shared. If one instance modifies the prototype properties, the prototype properties of another instance will also be modified
let child2 = new Child(); child.__proto__.age=10; console.log(child2.age);//10
Constructor inheritance
function Father(name){ this.name=name; this.printName=function(){ console.log(this.name); } } Father.prototype.age=40; function Child(){ Father.call(this); this.name='child'; } let child = new Child(); console.log(child);//Child { name: 'child', printName: [Function] } console.log(child.age);//undefined console.log(child instanceof Father);//false console.log(child instanceof Child);//true
The attributes in the parent class constructor are directly added to the constructor of the child class to realize multi inheritance. When a child class instance is generated, the instance contains all the attributes defined in all the parent class constructors
Disadvantages: 1. Only the properties of the parent constructor can be inherited, and the prototype properties of the parent cannot be obtained (because the prototype chain from the subclass prototype to the parent prototype does not exist).
2. It is impossible to reuse the constructor of the parent class. Each time a subclass instance is created, the constructor of the parent class must be called again. This causes each new instance to have a copy of the attribute of the parent constructor, which is cumbersome.
Combinatorial inheritance
function Father(name){ this.name=name; this.printName=function(){ console.log(this.name); } } Father.prototype.age=40; function Child(name){ Father.call(this,name);//The first call to the parent constructor } //Compared with the above example, only the following line of code is added to establish the link between the subclass and the parent class by using the prototype chain Child.prototype=new Father('father');//Call the parent constructor the second time let child = new Child('child'); console.log(child);//{ name: 'child', printName: [Function] } console.log(child.age);//40 console.log(child instanceof Father);//true console.log(child instanceof Child);//true
Composite inheritance combines the characteristics of the previous two modes, but calls the constructor of the parent class twice, resulting in memory loss
Parasitic combinatorial inheritance
function Father(name){ this.name=name; this.printName=function(){ console.log(this.name); } } Father.prototype.age=40; Father.prototype.printAge=function(){ console.log(this.age); } //Get parent prototype properties function content(obj){ function Foo(){}; Foo.prototype=obj; return new Foo(); } let father=content(Father.prototype) //Gets the parent class constructor property function Child(){ Father.call(this,'child'); } Child.prototype=father; Child.prototype.constructor=Child;//Repair constructor let child=new Child(); console.log(child);//{ name: 'child', printName: [Function] } console.log(child.age);//40 console.log(child instanceof Father);//true console.log(child instanceof Child);//true
Through the parasitic method, the instance properties of the parent class are cut off. In this way, when calling the construction of the parent class twice, the instance methods and properties will not be initialized twice, avoiding the disadvantage of combined inheritance