Write in front
This article only deals with prototype-based inheritance. After ES6, the inheritance based on Class es is referred to the relevant literature.
Knowledge Reserve
Two ways of calling constructors (with completely different results)
- Call by keyword new:
function Person(name) { this.name = name; this.age = 18; } var o = new Person('hx'); console.log(o.name, o.age); // hx 18 console.log(window.name, window.age); // '' undefined
- Direct call:
function Person(name) { this.name = name; this.age = 18; } var o = Person('hx'); console.log(o); // undefined console.log(window.name, window.age); // hx 18
Thus it can be seen:
- The constructor is the same as the ordinary function. It can be called directly and has no return value. this points to Window.
- With a new call, the return value is an object, and this points to that object.
What on earth did new do?
The new keyword does the following:
- Create an empty object;
- Link the object to another object (that is, set the constructor of the object);
- Take the empty object created in the first step as the context of this (this points to the empty object);
- Execute the constructor (add attributes to the object) and return the object
function Person(name) { this.name = name; this.age = 18; } var o = new Person('hx');
The four steps corresponding to the above code are:
- var obj = {};
- obj.__proto__ = Person.prototype;
- Person.call(obj,'hx');
- return obj;
Several Ways to Implement Inheritance in JavaScript
1. Prototype Chain Inheritance
function Parent(name) { this.name = name; this.age = 18; this.arr = ['hello','world'] } Parent.prototype.sayAge = function() { console.log(this.age) } function Child(gender) { this.gender = gender; } Child.prototype = new Parent(); var child1 = new Child('male'); child1.arr.push('js') console.log(child1.name); // undefined console.log(child1.age); // 18 console.log(child1.arr); // ['hello','world','js'] console.log(child1.gender); // male child1.sayAge(); // 18 var child2 = new Child('female'); console.log(child2.name); // undefined console.log(child2.age); // 18 console.log(child2.arr); // ['hello','world','js'] console.log(child2.gender); // female child2.sayAge(); // 18
Advantage:
- Methods on Parent prototype objects can be inherited by Child
Disadvantages:
- Reference type attributes of Parent are shared by all Child instances and interfere with each other.
- Child can't pass on to Parent
2. Constructor Inheritance (Classical Inheritance)
function Parent(name) { this.name = name; this.age = 18; this.arr = ['hello','world']; this.sayName = function() { console.log(this.name) } } Parent.prototype.sayAge = function() { console.log(this.age) } function Child(name,gender) { Parent.call(this,name); // this is pointed by Window s to the object to be created this.gender = gender; } var child1 = new Child('lala','male'); child1.arr.push('js'); console.log(child1.name); // lala console.log(child1.age); // 18 console.log(child1.arr); // ['hello','world','js'] console.log(child1.gender); // male child1.sayName(); // 18 child1.sayAge(); // Uncaught TypeError: child1.sayAge is not a function var child2 = new Child('fafa','female'); console.log(child2.name); // fafa console.log(child2.age); // 18 console.log(child2.arr); // ['hello','world'] console.log(child2.gender); // female child2.sayName(); // 18 child2.sayAge(); // Uncaught TypeError: child1.sayAge is not a function
Advantage:
- Avoid sharing reference type attributes with all Child instances
- Child can refer to Parent
Disadvantages:
- Methods on Parent prototype objects cannot be inherited by Child
- Every time you create a Child instance, you create a sayName method, which wastes memory resources.
3. Combinatorial Inheritance
function Parent(name,age) { this.name = name; this.age = age; this.arr = ['hello','world'] } Parent.prototype.sayName = function() { console.log(this.name) } function Child(name,age,gender) { Parent.call(this,name,age); this.gender = gender } Child.prototype = Object.create(Parent.prototype); Child.prototype.constuctor = Child; Child.prototype.sayAge = function() { console.log(this.age) } var child1 = new Child('lala',18,'male'); child1.arr.push('js'); child1.name; // 'lala' child1.age; // 18 child1.arr; // ['hello','world','js'] child1.gender; // 'male' child1.sayName(); // lala child1.sayAge(); // 18 var child2 = new Child('fafa',28,'female'); child1.name; // 'fafa' child1.age; // 28 child1.arr; // ['hello','world'] child1.gender; // 'female' child1.sayName(); // fafa child1.sayAge(); // 28
Composite inheritance is the best practice of JavaScript inheritance
- Attribute Inheritance Using Constructor - Avoid Parent Reference Attributes Influenced by Multiple Child Instances, and Support Passing Parameters
- Method Using prototype chain inheritance-supporting Child inheritance Parent prototype object method, avoiding duplicate copies of methods in multiple instances.
Supplement 1
There are two types of methods for combining Child.prototype = Object.create(Parent.prototype) in inheritance code:
Child.prototype = Parent.prototype or Child.prototype = new Parent().
- Child.prototype = Parent.prototype: This is definitely not possible, adding methods to Child.prototype or affecting Parent;
- Child.prototype = new Parent(): This approach has a disadvantage that two Parent constructors (one is new Parent() and the other is Parent.call(this,name)) are called when a new Child instance is called, which wastes efficiency, and if the Parent constructor has side effects, repeated calls may have adverse consequences.
For the second case, besides using Object.create(Parent.prototype) method, it can also be implemented with a bridge function. In fact, regardless of the method, its realization idea is to adjust the prototype chain:
By:
new Child() ----> Child.prototype ----> Object.prototype ----> null
The adjustment is as follows:
new Child() ----> Child.prototype ----> Parent.prototype ----> Object.prototype ----> null
function Parent(name) { this.name = name } Parent.prototype.sayName = function() { console.log(this.name) } function Child(name,age) { Parent.call(this,name); this.age = age; } function F() { } F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constuctor = Child; Child.prototype.sayAge = function() { console.log(this.age) }
Thus, through a bridge function F, the Parent constructor is invoked only once, thus avoiding creating unnecessary and redundant attributes on Parent.prototype.
// Encapsulate the above method function object(o) { function F() {} F.prototype = o; return new F(); } function prototype(child, parent) { var prototype = object(parent.prototype); child.prototype = prototype; prototype.constructor = child; } // When we use: prototype(Child, Parent);
Supplement 2
What is the best way to inherit?
In fact, whether it is improved combinatorial inheritance (using Object.create or Object. setPrototype Of) or so-called parasitic combinational inheritance (using bridging function F), is not the key to answer this question.
The best way of inheritance embodies a design concept:
Regardless of static or dynamic attributes, the criteria for dividing dimensions are whether they are shared or not.
- For each subclass, but the instances of subclasses are independent of each other (non-shared): the ++ should be placed on the constructor of the parent class, and then initialized by calling the constructor of the parent class through the subclass;
- For each subclass, and the subclass instance can share attributes (whether static attributes or dynamic attributes): should be +++ placed on the parent class prototype object, obtained through the prototype chain;
- For each subclass, and the subclass instances are independent attributes (non-shared): it should be + + implemented on the construction method of the subclass;
- The attributes that are unique to each subclass, but can be shared by instances of subclasses: should ++ be placed on the prototype object of the subclass, and obtained through the prototype chain;
From the text is not easy to understand, look at the code:
function Man(name,age) { // Each subclass has its own, but independent (non-shared) this.name = name; this.age = age; } Man.prototype.say = function() { // Each subclass has and shares dynamic attributes (sharing) console.log(`I am ${this.name} and ${this.age} years old.`) } // Static attributes (shared) that each subclass has and shares Man.prototype.isMan = true; function Swimmer(name,age,weight) { Man.call(this,name,age); // Swimmer subclasses are unique and each instance is independent (non-shared) this.weight = weight; } function BasketBaller(name,age,height) { Man.call(this,name,age); // BasketBaller subclasses are unique and each instance is independent (non-shared) this.height = height; } // Using ES6 to directly set up prototype relationships to build prototype chains Object.setPrototypeOf(Swimmer.prototype, Man.prototype) // Equivalent to Swimmer.prototype = Object.create(Man.prototype); Swimmer.prototype.constructor = Swimmer; Object.setPrototypeOf(BasketBaller.prototype, Man.prototype) // Equivalent to BasketBaller.prototype = Object.create(Man.prototype); BasketBaller.prototype.constructor = BasketBaller; // Continue to extend subclass prototype objects Swimmer.prototype.getWeight = function() { // Swimmer subclasses are unique, but share dynamic properties (sharing) console.log(this.weight); } // Swimmer subclasses are unique, but share static properties (shared) Swimmer.prototype.isSwimmer = true; var swimmer1 = new Swimmer('swimmer1',11,100); var swimmer2 = new Swimmer('swimmer2',21,200); swimmer1; // Swimmer {name: "swimmer1", age: 11, weight: 100} swimmer1.isMan; // ture swimmer1.say(); // I am swimmer1 and 11 years old. swimmer1.isSwimmer; // ture swimmer1.getWeight(); // 100 swimmer2; // Swimmer {name: "swimmer2", age: 21, weight: 200} swimmer2.isMan; // ture swimmer2.say(); // I am swimmer2 and 21 years old. swimmer2.isSwimmer; // ture swimmer2.getWeight(); // 200 // BasketBaller's Identity (Brief)