There are a lot of things in JS that are not easy to understand, like closures, inheritance, this. I’m going to crumple them up and export them separately.

This article is about inheritance

Why inheritance

Start with Brendan Eich, founder of JS

JS is also an object-oriented language, everything is an object, there must be a mechanism to link all objects together. But Brendan Eich’s founders didn’t want to introduce “classes” into JS, because once they introduced “classes”, JS would become a full object-oriented programming language, which was too formal for the purpose of creating JS. Influenced by the new command in C++ and JAVA, he introduced the new command to JS to generate an instance object from a constructor. See JS object – oriented encapsulation for details

The introduction of the prototype

Since instance objects that are directly new have their own copies of properties and methods, data cannot be shared and memory is wasted. With this in mind, Brendan Eich decided to set a Prototype property for the constructor. Put all properties and methods that need to be shared inside this Prototype object; Those unique properties and methods are inside the constructor. Once the instance object is created, it will have two classes of properties and methods, one of its own and one found through the prototype chain (along prototype).

conclusion

Inheritance means that a subclass can use all the functionality of its parent class and extend those functionality. For example, if constructor B wants to use properties and functions from constructor A, one way is to directly copy the code from constructor A, copy and paste it. Another way is to use inheritance, where B inherits FROM A, so that B can use the functionality in A

So what are the inheritance methods? What are the pros and cons? Just listen to me…

Prototype chain inheritance

grammar

Points a subclass’s prototype object to an instance of its parent class.

Child.prototype = new Parent()
Copy the code

The sample

function Parent(name,gender) {
	this.name = name;
	this.gender = gender;
}
Parent.prototype.getInfo = function() {
	console.info('my name is ' + this.name + '; I am a ' + this.gender )
}
function Child(age) {
	this.age = age
}
Child.prototype = new Parent("parent"."boy")
var child1 = new Child(30)
console.info(child1) // Child {age:30}
child1.getInfo() // my name is parent; I am a boy
console.info(child1.name) // parent
Copy the code

Analysis:

  1. The default Child instance’s prototype object points to child.prototype, after
Child.prototype = new Parent("parent"."boy")
Copy the code

Set Child’s prototype object to an instance of Parent as follows:According to the diagram, the child instance is availableprotoProperty pointing to the parent instance, under the parent instanceprotoProperty to parent.prototype. So child1 can access properties and methods of the parent instance (defined in the parent constructor) as well as properties and methods defined on parent. Prototype

  1. The first time I saw the name of the prototype chain inheritance, I thought it was using the prototype object of the subclass to point to the prototype object of the parent class.
Child.prototype = Parent.prototype
Copy the code

Change the code to:

function Parent(name,gender) {
	this.name = name;
	this.gender = gender;
}
Parent.prototype.getInfo = function() {
	console.info('my name is ' + this.name + '; I am a ' + this.gender ) 
}
function Child(age) {
	this.age = age
}
Child.prototype = Parent.prototype
var child1 = new Child(30)
console.info(child1) // Child {age:30}
child1.getInfo() // my name is parent; I am a boy
console.info(child1.name) // undefined
Copy the code

The prototype chain diagram becomes:Obviously, the Child instance cannot access the properties and methods defined in the Parent constructor, only the properties and methods defined in the Parent prototype object, which is clearly not true inheritance

Summary of prototype chain inheritance

function Parent(name,gender) {
	this.name = name;
	this.gender = gender;
	this.colors= ["blue"."pink"] // Add the reference type attribute colors
}
Parent.prototype.getInfo = function() {
	console.info('my name is ' + this.name + '; I am a ' + this.gender ) 
}
function Child(age) {
	this.age = age
	this.sports = ["basketball"]  // Add the reference type attribute sports
}
var parent = new Parent("parent"."boy")
Child.prototype = parent 
var child1 = new Child(30."child1 name") // You want to pass child1 name as a unique name for this instance
child1.gender = "girl"
child1.colors.push("green")
child1.sports.push("football")
var child2 = new Child(29."child2 name") // You want to pass child2 name as a unique name for this instance
console.info(child1) // Child {age: 30, sports: ["basketball", "football"], gender: "girl"}
console.info(child2) // Child {age: 29, sports: ["basketball"]}
console.log(child1.name) // parent
console.info(child2.colors) // ["blue", "pink", "green"]

Copy the code

Analysis:

  • Add the colors attribute in the Parent constructor and the sports attribute in the Child constructor
  • Two Child instances are created (child1 and child2).
  • After child1 is created, gender is set and content is pushed for both Colors and Sports.
  • After child2 is created, nothing is done
  • Gender = “girl” adds gender to child1 instance, so that when you get gender from child1, you get its own gender value instead of looking up the stereotype
  • Child1.colors.push (“green”) color.push (“green”) color.push (“green”) color.push (“green”) color.push (“green”) color.push (“green”) color.push (“green”) So you add content to the colors attribute of the parent instance, which affects the parent and all instantiated Child instances
  • Sports is a built-in property on Child, so pushing on child1 does not affect other instances (such as Child2).
  • So child1 prints out age, sports, and the new gender property. Child2 prints only age and Sports
  • Child1. name prints ‘parent’ because child1.name actually accesses the name of the prototype object parent. Although ‘child1 name’ is passed in the new Child, it obviously has no effect because the name attribute is not received in the Child constructor
  • Child2. colors actually calls the colors of the prototype object parent, and colors has been changed by child1 to [“blue”, “pink”, “green”].

Advantages:

  • Inheriting properties and methods from the parent constructor, and inheriting properties and methods from the parent prototype object

Disadvantages:

  • Multiple inheritance cannot be implemented (because stereotype objects are specified)
  • All attributes from the prototype object are shared, and when an attribute of a reference type is changed, all instance objects are affected (this can be seen from child2.colors).
  • I can’t pass arguments to the parent object. For example, if I want to set a unique name value, I have to redefine the name property in my constructor to mask the parent object’s name.

Constructor inheritance

grammar

Use call or apply inside the subclass constructor to call the superclass constructor

function Child() {
	Parent.call(this. arguments) }Copy the code

To enhance a subclass, copy the instance properties of the parent class to the subclass

The sample

function Parent(name,gender) {
	this.name = name;
	this.gender = gender;
}
function Child(age,name,gender) {
	this.age = age
	Parent.call(this,name,gender) // Equivalent to Parent. Apply (this,[name,gender])
}
var child1 = new Child(30."child1 name"."girl")
console.info(child1) // Child {age: 30, name: "child1 name", gender: "girl"}
console.info(child1.name) // child1 name
console.info(child1 instanceof Child) // true
console.info(child1 instanceof Parent) // false
Copy the code

Analysis:

  1. In the subclass constructor, the superclass constructor is executed directly. It adds all the attributes of the parent class to the constructor of the subclass.
  2. This inheritance borrows the capabilities of the Call or apply functions.
  3. There is no connection between the Child instance and the Parent.

Constructor inheritance summary

function Parent(name) {
	this.name = name;
	this.gender = gender;
	this.colors= ["blue"."pink"]
}
Parent.prototype.getInfo = function() {
	console.info('my name is ' + this.name + '; I am a ' + this.gender ) 
}
function Child(age,name,gender) {
	this.age = age
	this.sports = ["basketball"]  
	Parent.call(this,name,gender)
}
var child1 = new Child(30."child1 name"."girl") 
child1.gender = "girl"
child1.colors.push("green")
child1.sports.push("football")
var child2 = new Child(29."child2 name") 
console.info(child1) // Child {age: 30, sports: ["basketball", "football"], gender: "girl", name: "child1 name", colors: ["blue", "pink", "green"]}
console.info(child2) // Child {age: 29, sports: ["basketball"], gender: undefined, name: "child2 name", colors: ["blue", "pink"]}
console.log(child1.name) // child1 name
console.info(child2.colors) // ["blue", "pink"]
child1.getInfo() //VM1106:1 Uncaught TypeError: child1.getInfo is not a function
Copy the code

Analysis:

  • Since the parent constructor is executed immediately in the subclass constructor, attributes such as colors, name, gender, and so on are the subclass’s own. Doing anything does not affect other instances, so child1.colors.push() does not affect other instances
  • GetInfo is the method on the superclass prototype object. The call function simply executes the properties and methods of the superclass constructor. It does not copy the properties and methods on the superclass prototype object, so an error is reported

Advantages: Solves the disadvantages of prototype chain inheritance

  • Multiple inheritance is possible (just call the parent constructor separately in the subclass constructor)
  • [Fixed] subclass instances sharing parent class constructors that reference type attributes (as can be seen from child.colors)
  • You can pass arguments to the parent class (this can be seen from child1.name)

Disadvantages: Solves the disadvantages of the prototype chain inheritance, but at the same time eliminates the advantages of the prototype chain.

  • Cannot inherit properties and methods from superclass prototype objects
  • There is no connection between a child instance and its Parent.
  • Function reuse cannot be realized. For function attributes in the parent class, each subclass will copy a copy, affecting performance

Combination of inheritance

Since both stereotype chain inheritance and constructor inheritance have their advantages and disadvantages, can we combine the two?

grammar

// Prototype chain inheritance
Child.prototype = new Parent()
// Constructor inheritance
function Child() {
	Parent.call(this. arguments) }Copy the code
  • Use stereotype chain inheritance to ensure that subclasses inherit properties and methods from their parent class
  • Constructor inheritance is used to ensure that subclasses can inherit properties and methods from the parent constructor

The base operation:

  • Call the superclass constructor inside the subclass constructor via call/apply
  • Points the prototype object of the subclass constructor to an anonymous instance created by the superclass constructor
  • Fix the constructor property of the subclass constructor prototype object to point it to the subclass constructor

The sample

function Parent(name){
	this.name = name;
}
Parent.prototype.getInfo = function() {
	console.info('my name is ' + this.name)
}
function Child(name) {
	Parent.call(this,name) // Constructor inheritance
}
Child.prototype = new Parent() // The prototype chain inherits, passing empty arguments is enough, because any arguments passed will be masked
Child.prototype.constructor = Child // Fix child1 constructor pointing to Parent
var child1 = new Child("child1 name")
console.info(child1) // Child {name: "child1 name"}
child1.getInfo() // my name is child1 name
console.info(child1.constructor) ƒ Child(name) {Parent. Call (this,name)}
Copy the code

Analysis:

  • The property and method of the parent class prototype object can be reused by inheritance of prototype chain. Just pass null arguments, because any passed arguments are masked
  • Constructor inheritance inherits attributes and methods from the parent constructor, while masking attributes and methods from the subclass instance __proto__(the parent instance)
  • By the Child. The prototype. The constructor = Child is actually just a tag, identify an instance is produced by which the constructor. As a matter of programming custom, it is best to change this to the correct constructor

The prototype chain diagram is modified to look like this (there are two more lines than the prototype chain inheritance, one is the Child call/apply call Parent; Constructor points to Child. Prototype (Parent instance)

Summary of combinatorial inheritance

function Parent(name, colors) {
	this.name = name
	this.colors = colors
}
Parent.prototype.sports = ['basketball']
function Child(name, colors) {
	Parent.call(this, name, colors)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child

var child1 = new Child('child1'['pink'])
child1.colors.push('blue')
child1.sports.push('football')
var child2 = new Child('child2'['black'])

console.info(child1) // Child {name: "child1", colors: ["pink", "blue"]}
console.info(child2) // Child {name: "child2", colors: ["black"]}
console.info(child2.sports) // ["basketball", "football"]
console.info(Child.prototype) // Parent {name: undefined, colors: undefined, constructor: ƒ Child(name, colors){}

console.info(child1 instanceof Child) // true
console.info(child1 instanceof Parent) // true
Copy the code

Analysis:

  • The colors on both instances are inherited from the parent class through the constructor, and are copied properties that do not interact with each other
  • Sports is a property on a parent prototype object and is shared, so change child1. Sports affects other Child instances
  • Child.prototype is generated using new Parent and no arguments are passed, so name and colors are undefined. And refer constructor back to Child
  • Because child1 can look up child. prototype and parent-prototype along its prototype chain. So the last two are true

Advantages: equivalent to a combination of the advantages of prototype chain inheritance and constructor inheritance:

  • You can inherit attributes and methods of a parent instance, as well as attributes and methods of a parent prototype object
  • Like constructor inheritance, it solves the problem of sharing referenced type attributes in superclass constructors
  • As with constructor inheritance, arguments can be passed to the parent

Is this combinatorial inheritance perfect? The answer is no. Find out

function Parent(name) {
	console.info(name)
	this.name = name
}
function Child(name) {
	Parent.call(this, name)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child

var child1 = new Child('child1')
console.info(child1)
console.info(Child.prototype)
Copy the code

The result is:

// undefined
// child1

// Child {name:'child1'}
// Parent {name:'undefined',constructor:f Child(name){}}
Copy the code

Analysis:

  • The Parent function is called once when the new Parent function is called. The Child constructor also calls the Parent function once, passing in child1 as the name argument
  • Constructor (); / / constructor (); / / constructor (); / / constructor ()

Disadvantages:

  • The constructor of the parent class is called twice
  • Attributes and methods in subclass instances mask attributes and methods on parent instances, increasing unnecessary memory

Primary inheritance

grammar

function create(o) {
	function F() {}
	F.prototype = o
	return new F()
}
Copy the code
  • A temporary constructor is created inside the create function, and a new instance of the temporary constructor is returned using the object passed as a prototype object.
  • Essentially, create() makes a shallow copy of the object passed in.
  • You can modify the returned object as appropriate

The sample

function create(o) {
	function F() {}
	F.prototype = o
	return new F()
}
var person = {
	name: 'Joe'.colors: ['pink'.'blue']}var student = create(person)
student.name = 'Ming'
student.colors.push('yellow')
var teacher = create(person)
teacher.name = 'Miss Xia'
teacher.colors.push('green')
console.info(student) // F {name: "xiao Ming "}
console.info(teacher) // F {name: ""}
console.info(teacher.colors) // ["pink", "blue", "yellow", "green"]
Copy the code

Analysis:

  • Using Person as a prototype object, instantiate student and teacher, and the __proto__ attribute of student and teacher instances points to Person
  • Student. Name creates the name attribute and does not affect the name attribute in person
  • Student. Colors. Push is a search for colors on the prototype object, person, and affects person and all instance objects (e.g., teacher).

** The original type after ES5 inherits ** ES5 new Object.create Method normalizes the original type inheritance.

var person = {
	name: 'Joe'.colors: ['pink'.'blue']}var student = Object.create(person)
student.name = 'Ming'
student.colors.push('yellow')
var teacher = Object.create(person)
teacher.name = 'Miss Xia'
teacher.colors.push('green')
console.info(student) // F {name: "xiao Ming "}
console.info(teacher) // F {name: ""}
console.info(teacher.colors) // ["pink", "blue", "yellow", "green"]
Copy the code

Summary of original type inheritance

Advantages:

  • Compared to prototype chain inheritance, there is less code and no need to create constructors

Disadvantages: Similar to prototype chain inheritance

  • Shared inheritance between instances Instances reference type attributes (as can be seen from teach.colors)
  • You cannot pass arguments to the parent constructor, but can only overwrite it. After overwriting, the parent and child will have the same property problem, resulting in memory waste

Parasitic inheritance

grammar

function createAnother(original){ 
 let clone = Object.create(original); Create a new object by calling the function
 clone.fn = function() {};  // Enhance the object in some way
 return clone; // Return this object
}
Copy the code

The idea is to encapsulate an object on top of the original type inheritance, enhance the object, and then return the object

The sample

function createAnother(original){ 
 let clone = Object.create(original); Create a new object by calling the function
 clone.sayHi = function() { // Enhance the object in some way
 	console.log("hi"); 
 }; 
 return clone; // Return this object
}
var person = {
	name: 'Joe'.colors: ['pink'.'blue']}var student = createAnother(person)
student.sayHi() // hi
Copy the code

Summary of parasitic inheritance

Basically the same advantages as native inheritance:

  • Compared to prototype chain inheritance, there is less code and no need to create constructors

Disadvantages: Similar to prototype chain inheritance

  • Shared inheritance between instances Instances reference type attributes (as can be seen from teach.colors)
  • You cannot pass arguments to the parent constructor, but can only overwrite it. After overwriting, the parent and child will have the same property problem, resulting in memory waste
  • Adding a function to an object causes the function to be difficult to reuse and wastes memory

Parasitic combinatorial inheritance

grammar

To fix the shortcomings of composite inheritance:

  1. The superclass constructor is called twice
  2. Two instances are generated, generating useless deprecated properties on the parent instance

, so parasitic combinatorial inheritance is introduced. In fact, it is already possible to inherit properties and methods from the parent constructor through constructor inheritance, so just inherit properties and methods from the parent prototype object.

function Child() {
	Parent.call(this. arguments) } Child.prototype =Object.create(Parent.prototype)
Copy the code

The sample

function Parent(name) {
	this.name = name
}
Parent.prototype.getInfo = function () {
	console.info(this.name)
}
function Child(name) {
	this.sex = 'boy'
	Parent.call(this, name)
}
// The difference with composite inheritance
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
var child1 = new Child('child1')
console.info(child1) // Child {sex: "boy", name: "child1"}
child1.getInfo() // child1
console.info(child1.__proto__) // Parent {}
Copy the code

Analysis:

  • Child1 has nothing to do with Parent. It simply copies the properties and methods of Parent in the constructor with parent-call, so that Parent is called only once
  • Parent {} does not have the name attribute, so there is no memory waste

Parasitic combination inheritance summary

function Parent(name, colors) {
	this.name = name
	this.colors = colors
}
Parent.prototype.sports = ['basketball']
function Child(name, colors) {
	Parent.call(this, name, colors)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

var child1 = new Child('child1'['pink'])
child1.colors.push('blue')
child1.sports.push('football')
var child2 = new Child('child2'['black'])
child2.sports = ['badminton']

console.info(child1) // Child {name: "child1", colors: ["pink", "blue"]}
console.info(child2) // Child {name: "child2", colors: ["black"], sports: ["badminton"]}
Copy the code

Analysis:

  • The name and colors attributes are both copied from the constructor, so changing child1.colors has no effect on other instances
  • Child1. Sports is to add content to the sports on the prototype chain, which will affect parent and child2. That is, child2. Sports gets the push value. If you execute “child2.sports”, you add “sports” to the “child2” object, overwriting the “sports” attribute on the prototype object, so “Child2. sports” is its own attribute

Advantages:

  • The parent constructor is called only once, and only one copy of the parent property is created
  • Subclasses can use properties and methods from the parent class’s prototype chain

The class inheritance

grammar

Class inheritance depends on two things: extends and super

class Parent {}
class Child extends Parent {}Copy the code

Compiled with Babel:

// Add some comments
'use strict'
Typeof processing of Symbol is supported and not supported for the current environment
function _typeof(obj) {
	'@babel/helpers - typeof'
	if (
		typeof Symbol= = ='function' &&
		typeof Symbol.iterator === 'symbol'
	) {
		_typeof = function _typeof(obj) {
			return typeof obj
		}
	} else {
		_typeof = function _typeof(obj) {
			return obj &&
				typeof Symbol= = ='function' &&
				obj.constructor === Symbol&& obj ! = =Symbol.prototype
				? 'symbol'
				: typeof obj
		}
	}
	return _typeof(obj)
}
// The core of parasitic combinatorial inheritance
function _inherits(subClass, superClass) {
	if (typeofsuperClass ! = ='function'&& superClass ! = =null) {
		throw new TypeError(
			'Super expression must either be null or a function')}The object.create () method creates a new Object, using the existing Object as the prototype of the newly created Object (__proto__).
	Subclass.prototype. __proto__ === superclass.prototype; This statement is true
	subClass.prototype = Object.create(superClass && superClass.prototype, {
		constructor: { value: subClass, writable: true.configurable: true}})if (superClass) _setPrototypeOf(subClass, superClass)
}
/ / set __proto__
function _setPrototypeOf(o, p) {
	_setPrototypeOf =
		Object.setPrototypeOf ||
		function _setPrototypeOf(o, p) {
			o.__proto__ = p
			return o
		}
	return _setPrototypeOf(o, p)
}

function _createSuper(Derived) {
	var hasNativeReflectConstruct = _isNativeReflectConstruct()
	return function _createSuperInternal() {
		var Super = _getPrototypeOf(Derived),
			result
		if (hasNativeReflectConstruct) {
			var NewTarget = _getPrototypeOf(this).constructor
			result = Reflect.construct(Super, arguments, NewTarget)
		} else {
			result = Super.apply(this.arguments)}return _possibleConstructorReturn(this, result)
	}
}
/ / _possibleConstructorReturn judgment Parent. Call (this name), or function return value is null or function object.
function _possibleConstructorReturn(self, call) {
	if (
		call &&
		(_typeof(call) === 'object' || typeof call === 'function')) {return call
	}
	return _assertThisInitialized(self)
}
// if self is void 0 (undefined
function _assertThisInitialized(self) {
	if (self === void 0) {
		throw new ReferenceError(
			"this hasn't been initialised - super() hasn't been called")}return self
}

function _isNativeReflectConstruct() {
	if (typeof Reflect= = ='undefined' || !Reflect.construct) return false
	if (Reflect.construct.sham) return false
	if (typeof Proxy= = ='function') return true
	try {
		Boolean.prototype.valueOf.call(
			Reflect.construct(Boolean[],function () {}))return true
	} catch (e) {
		return false}}/ / get __proto__
function _getPrototypeOf(o) {
	_getPrototypeOf = Object.setPrototypeOf
		? Object.getPrototypeOf
		: function _getPrototypeOf(o) {
				return o.__proto__ || Object.getPrototypeOf(o)
		  }
	return _getPrototypeOf(o)
}
The instanceof operator contains manipulation of Symbol
function _instanceof(left, right) {
	if( right ! =null &&
		typeof Symbol! = ='undefined' &&
		right[Symbol.hasInstance]
	) {
		return!!!!! right[Symbol.hasInstance](left)
	} else {
		return left instanceof right
	}
}

function _classCallCheck(instance, Constructor) {
	if(! _instanceof(instance, Constructor)) {throw new TypeError('Cannot call a class as a function')}}var Parent = function Parent() {
	_classCallCheck(this, Parent)
}

var Child = /*#__PURE__*/ (function (_Parent) {
	_inherits(Child, _Parent)

	var _super = _createSuper(Child)

	function Child() {
		_classCallCheck(this, Child)

		return _super.apply(this.arguments)}return Child
})(Parent)
Copy the code

The core code is the _inherits function, which is similar to the parasitic combination inheritance, except that _setPrototypeOf is added at the end. The purpose of this code is to inherit static methods from the parent class, which is not available in the above inheritance methods. That is, class inheritance is essentially parasitic combinatorial inheritance, with one more step to inherit the static methods of the parent class.

The sample

class Parent{
    constructor(name){
        this.name = name;
    }
    static sayHello(){
        console.log('hello');
    }
    sayName(){
        console.log('my name is ' + this.name);
        return this.name; }}class Child extends Parent{
    constructor(name, age){
        super(name);
        this.age = age;
    }
    sayAge(){
        console.log('my age is ' + this.age);
        return this.age; }}let parent = new Parent('Parent');
let child = new Child('Child'.18);

console.log('parent: ', parent); // parent: Parent {name: "Parent"}
Parent.sayHello(); // hello
parent.sayName(); // my name is Parent

console.log('child: ', child); // child: Child {name: "Child", age: 18}
Child.sayHello(); // hello
child.sayName(); // my name is Child
child.sayAge(); // my age is 18

Copy the code

Analysis:

  • Static sayHello methods are defined on Parent and sayName methods are defined on the Parent prototype
  • Extends makes a Child inherit from a Parent
  • Static methods that can access Parent on Child

There are two prototype chains:

// 1, constructor prototype chain
Child.__proto__ === Parent; // true
Parent.__proto__ === Function.prototype; // true
Function.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
// 2
child.__proto__ === Child.prototype; // true
Child.prototype.__proto__ === Parent.prototype; // true
Parent.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
Copy the code

Prototype chain diagram:ES6 extends inheritance, which basically means:

  • The proto of the Child constructor points to the Parent constructor,
  • Proto of child refers to the proto of parent. Prototype.
  • The Child constructor inherits attributes from the Parent constructor. Call with super (ES5 calls with call or apply).

Class Inheritance summary

Most of today’s ES6 class inheritance is syntactic sugar based on parasitic combinatorial inheritance in ES5