🙌 recently reviewed JS inheritance and found that some concepts were not very clear. Here’s a look at some of the main methods of JS inheritance. To better understand JS inheritance, you must understand the concepts of JS constructors, prototype objects, instantiated objects, and prototype chains.

This article is small in the review of JS, after consulting the documentation and their own example summary, welcome to discuss in the comments section. Of course, also hope to point in the friends, do not begrudge your thumb, to a 👍 like ➕ collection ➕ pay attention to the triple punch, this is the small series to continue to share the article the biggest power. Earlier I also shared THE JS package, if you are interested can have a look. It looks even better that way.

Review before class:

Object:

Before you can understand the concepts of prototype, prototype chain, and inheritance, you need to understand what an object is 🌼

An object is an abstraction of a single food

A book, a car, a person can all be objects, as can a database, a web page, a connection to a remote server. When objects are abstracted into objects, the relationship between objects becomes the relationship between objects, so that the real situation can be simulated and the object can be programmed.

An object is a container that encapsulates properties and methods.

Properties are the state of the object, methods are the behavior of the object. For example, we can abstract a Person as a Person object, which has attributes such as name, age, gender, etc., while running can be regarded as a method, which belongs to a behavior of people.

Var person = {name: 'ma ', age: 50, sex:' m ', running: function(){console.log(' I want to run ')}}Copy the code

Object prototypes and prototype chains

When it comes to inheritance, you have to talk about prototypes and prototype chains

Stereotypes: In javascript, functions can have attributes. Each function has a special attribute: prototype, whose value is an object.

  1. Each function has a prototype attribute; An attribute value is an object.
function Person() {}
console.log(Person.prototype)
Copy the code

  1. In the Prototype object, there is a constructor attribute by default, whose value is the current function itself

There are three methods to obtain the prototype object of the instance object obj:

  1. obj.proto
  2. obj.constructor.prototype
  3. Object.getPrototypeOf(obj) Introduction of use
Prototype = function(){console.log(this.name)} var p = new Person(); console.log(p.__proto__) console.log(p.constructor.prototype) console.log(Object.getPrototypeOf(p))Copy the code

But:

The __proto__ attribute needs to be deployed only for browsers, other environments do not need to deploy;

Obj. Constructor. The prototype in manual change the prototype object, may fail.

All properties and methods of the prototype object can be shared by the instance object;

The prototype object is used to define properties and methods shared by all instance objects.

The prototype chain:

JavaScript dictates that all objects have their own prototype object, and a prototype object is also an object, so it has its own prototype. Therefore, a prototype chain is formed: objects => prototypes => prototypes of prototypes

Does the object. prototype Object have a prototype? Object. Prototype is null. Null has no properties, no methods, and no prototype of its own. Thus, the end of the prototype chain is NULL.

We find that he finally turns on himself; Object.prototype.proto === null;

Object.getPrototypeOf(Object.prototype) //null
Copy the code

Null has no properties or methods and no prototype of its own. So the end of the prototype chain is NULL.

function Parent(age) {
    this.age = age;
}
var p = new Parent(50);

p;	// Parent {age: 50}
p.__proto__ === Parent.prototype; // true
p.__proto__.__proto__ === Object.prototype; // true
p.__proto__.__proto__.__proto__ === null; // true
Copy the code

Constructor:

The so-called constructor is the function that is specially used to generate instance objects. It is the template of the object and describes the basic structure of the object. A constructor can generate multiple instance objects, all of which have the same structure.

Constructor features:

  • The function body uses the this keyword, which represents the instance object to be generated;
  • When generating objects, you must use the new command;
  • To distinguish a constructor from a normal function, the first letter of its name is usually capitalized;
Function Person(){this.name = 'ma'}Copy the code

The constructor properties:

The Prototype object has a constructor property, which by default points to the constructor of the Prototype object.

The constructor property lets you know which constructor generated an instance object.

Function Person(){this.name = 'ma'} p.constructor === Person // trueCopy the code

Since the constructor property is defined on top of the Prototype object, that means it can be inherited by all instance objects.

Function Person(){this.name = 'myname'} var p = new Person(); p.constructor === Person // true p.constructor === Person.prototype.constructor // true p.hasOwnProperty('constructor') // falseCopy the code

The Constructor property represents the association between the stereotype object and the constructor, and if the stereotype object is modified, the Constructor property is typically modified as well.

function Person(name) { this.name = name; } Person.prototype.constructor === Person // true Person.prototype = { method: function () {} }; // Modify the constructor's prototype object here, So constructor property no longer points to the Person to Person. The prototype. The constructor = = = the Person / / false Person. The prototype. The constructor = = = Object / / trueCopy the code

Suggestion: When modifying the prototype object of a constructor, generally modify the point to the constructor property as well.

Person. The prototype. The constructor = Person;Copy the code

The instanceof operator:

The instanceof operator returns a Boolean value indicating whether the object is an instanceof a constructor.

var v = new Vehicle();
v instanceof Vehicle // true
Copy the code

The instanceof operator has the instance object on the left and the constructor on the right. It checks if the prototype object of the right constructor is in the prototype chain of the left. So the following two ways are equivalent.

V instanceof Vehicle / / is equal to the Vehicle. The prototype. IsPrototypeOf (v)Copy the code

The new command:

The new command executes a constructor that returns an instance object. When using the new command. Constructors can also take arguments as needed.

Function Person(){this.name = 'myname'} var p = new Person(); The console. The log (p.n ame) / / maCopy the code

Principle of the new command

When the new command is used, the function following it performs the following steps at once:

  • Create an empty object as an instance of the object to be returned.
  • Point the empty object’s prototype to the constructor’s Prototype property;
  • Assign the empty object’s value to the this key inside the function.
  • Start executing the code inside the constructor

That is, inside the constructor, this refers to a newly generated empty object on which all operations will be performed.

A natural question is what happens if you forget to use the new command and call the constructor directly?

This in the constructor represents a global object, and name becomes a global variable, so name can be printed out, and p becomes undefined

Function Person(){this.name = 'Person'} var p = Person(); Console. log(p) //undefined console.log(name) // Ma Console. log(p.name) // 'name' of undefinedCopy the code

During development, how do I avoid calling the constructor directly without using the new command?

  1. The constructor uses strict mode internally, with use strict on the first line. That way, if you forget to use the new command, calling the constructor directly will return an error.

  2. The constructor internally determines whether to use the new command and returns an instance object if it does not.

If there is a return statement inside the constructor

If the return statement returns a new object unrelated to this, the new command returns the new object, not this.

Function Person(){this.name = 'ma '; return {age: 23} } var p = new Person(); console.log(p) //{age: 23}Copy the code

Otherwise, this object is returned regardless of the return statement.

Function Person(){this.name = 'ma '; } var p = new Person(); Console. log(p) //Person {name: "jack "}Copy the code

The new command always returns an object, either an instance object or an object specified by the return statement.

new.target

You can use the new.target attribute inside the function. If the current function is called with the new command, new.target points to the current function, otherwise undefined.

function Person(){ if(! New.target){throw new Error(' Please call with new command! ')}else{this.name = 'ma'}} // var p = new Person(); Var p = Person(); //Uncaught Error: Please call with new command!Copy the code

Use this attribute to determine whether the new command is used when the function is called.

ES5 inheritance

Concept: Inheritance means that a subclass can use all the functionality of its parent class and extend those functionality.

Prototype chain inheritance

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

function Parent(name, sex){ this.name = name; this.sex = sex; } Parent.prototype.getInfo = function(){ console.log(`name: ${this.name}`) console.log(`sex: ${this.sex} ')} function Child (){this.name = 'I am Child'} var parent = new parent (' ma ',' male '); var child = new Child(); console.log(parent) console.log(child)Copy the code

See yet? The parent and child have nothing to do with each other.

Parent and child inherit parent attributes and methods.

function Parent(name, sex){
    this.name = name;
    this.sex = sex;
}
Parent.prototype.getInfo = function(){
    console.log(`name: ${this.name}`)
    console.log(`sex: ${this.sex}`)
}
function Child (){
    this.name = '我是child'
}
var parent = new Parent('马云','男');

Child.prototype = parent; //子类的原型对象指向父类的实例

var child = new Child();

console.log(parent) //name: 马云   sex: 男

console.log(child) //name:我是child  sex: 男

parent.getInfo() 

child.getInfo() 
Copy the code

The child inherits all of the parent’s attributes and methods.

Although all of the parent’s attributes and methods are on the child’s prototype and not directly displayed on the instance, the instance has direct access to the attributes and methods on the prototype.

If the child has the property, it uses its own property; if it doesn’t, it searches layer by layer until it finds it. Above it finds the property on the prototype.

Advantages and disadvantages of stereotype inheritance

function Parent(name, sex){ this.name = name; this.sex = sex; GetInfo = function(){console.log(' name: ${this.name} ') console.log(' sex: ${this.name} ') console.log(' sex: ${this.name} ') console.log(' sex: ${this.name} ') ${this.sex}`) } function Child (name){ this.name = name; } var parent = new parent (' jack ',' male '); Child.prototype = parent; var lisa = new Child('lisa'); lisa.sex = 'girl'; var jack = new Child('jack', 'boy'); Log (Lisa) console.log(jack) console.log(lisa.sex) //girl console.log(Lisa.hobby) //[" run ", "Swimming", "painting" is] the console. The log (jack. Sex) / / male console. The log (jack. Hobby) / / [" running ", "swimming", "painting"]Copy the code

Resolution:

  1. lisa.sex = ‘girl’; Lisi adds a sex attribute to its instance, regardless of whether it has a sex attribute before, so it will print girl. If an attribute has been modified before, the new attribute overwrites the previous one.
  2. Jack.hobby. Push (‘ draw ‘) finds the property, finds that you don’t have it, finds the hobby property on the prototype, and adds a new one. So lisa.hobby changed. Because Lisa and Jack share a prototype object.
  3. The Child constructor does not receive the sex attribute, so it does not have the sex attribute.

Jack.hobby. Push (' draw ') is a hobby that doesn't affect the prototype.

function Parent(name, sex){ this.name = name; this.sex = sex; GetInfo = function(){console.log(' name: ${this.name} ') console.log(' sex: ${this.name} ') console.log(' sex: ${this.name} ') console.log(' sex: ${this.name} ') ${this.sex}`) } function Child (name){ this.name = name; } var parent = new parent (' jack ',' male '); Child.prototype = parent; var lisa = new Child('lisa'); lisa.sex = 'girl'; var jack = new Child('jack', 'boy'); Jack. hobby = [' tea ']; Jack. Hobby. Push (' drawing ') console. The log (lisa. Hobby) / / / "running", "swimming" console, log (jack. Hobby) / / / "drink tea", "painting"Copy the code

Jack. hobby = [‘ tea ‘]; In this case, Jack adds a new hobby property to himself and then pushes his hobby property, so it will not affect Lisa. Lisa uses the Hobby property on the prototype. The pros and cons of archetypal inheritance are clear.

Summary of prototype chain inheritance

How to do it: The prototype object of a subclass points to an instance of the parent class.

Child. Prototype = new Parent(' jack ',' male ');Copy the code

✅ Advantages: Can inherit all attributes and methods of the parent class;

❌ faults:

  1. A subclass cannot pass arguments to its parent class.
  2. If you want to add attributes and methods to a Child class’s prototype, you must place them after statements like child.prototype = new Parent();
  3. Properties on the parent stereotype chain can be shared by multiple instances, causing one instance to modify the stereotype and the others to change.

Constructor inheritance

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

  • Specify the binding object for this directly using call(), apply(), or bind(), such as foo.call(obj)

  • Functions that use.call() or.apply() are executed directly

  • Bind () creates a new function that needs to be called manually

  • Call () and.apply() are used in much the same way, except that call takes several arguments, whereas apply takes an array

Let’s start with the simplest constructor inheritance:

function Parent(name, sex){ this.name = name; Function Child(name,sex){Parent. Call (this,name, sex)} var Child = new Child(' Child ', 'girl'); console.log(child)Copy the code

Here’s another example:

function Parent(name, sex){ this.name = name; Sex = sex} function Child (sex){this. Sex = 'female' Parent. Call (this, 'Child ', Var child = new child ('girl'); console.log(child)Copy the code

Sex = girl; sex = girlThis. Sex = 'female'Overridden by the sex property of the parent class. This. name = ‘I am a subclass’ defines an attribute for itself.

Let’s change the value of the parent class and see if it’s affected by another instance, okay?

function Parent(name, sex){ this.name = name; this.sex = sex; This. Hobby = [' swim ',' run ']; Function Child(name, sex){Parent. Call (this, name, sex)} var lisa = new Child('lisa','girl'); Lisa.hobby. Push (' sing ') lisa.desc= 'I am Lisa' var Jack = new Child(' Jack ','boy') console.log(Lisa) console.log(Jack)Copy the code

See? Only the instance of LISA was modified, and no other instance objects were affected.

Doesn’t feel like constructor inheritance is super cool and doesn’t share instances.

Don’t constructors have disadvantages?

No, no, no.?

You read on

function Parent(name, sex){ this.name = name; this.sex = sex; This. Hobby = [' swim ',' run ']; GetInfo =function(){console.log(this.name, this.sex)} function Child (name, this.sex) sex){ Parent.call(this, name, sex) } var lisa = new Child('lisa','girl'); Lisa.hobby. Push (' sing ') lisa.desc= 'I am Lisa' var Jack = new Child(' Jack ','boy') console.log(Lisa) console.log(Jack) Lisa. GetInfo () / / an errorCopy the code

There is no getInfo method on the subclass’s prototype, so there is an error.

This illustrates a problem: construct inheritance can only inherit the instance properties and methods of the parent class, not the properties and methods of the parent class prototype.

Summarize constructor inheritance

How to do it: Call the parent constructor using call or apply inside the subclass constructor

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

✅ advantages:

  1. It ensures that the referenced type value in the prototype chain is independent and not shared by all instances.

  2. Subclasses can pass arguments to their parents;

❌ disadvantages: constructor inheritance can only inherit the attributes and methods of the parent class, not the stereotype of the parent class.

Combination of inheritance

Implementation method: use the prototype chain to achieve the inheritance of prototype attributes and methods, by borrowing the constructor to achieve the inheritance of instance attributes.

function Parent(name, sex){ this.name = name; this.sex = sex; This. Hobby = [' swim ',' run ']; This. desc = 'I am describing '; Console.log (' I'm parent')} parent.prototype.getInfo =function(){console.log(this.name, this.sex)} function Child (name, this.sex) Sex){Parent. Call (this, name, sex)} Child. Prototype = new Parent(); Var lisa = new Child('lisa','girl'); The console. The log (lisa) lisa. GetInfo () / / lisa girl Child. / / repair the constructor to prototype. The constructor = Child;Copy the code

Constructor it simply gives us a hint that the instance object was created by that constructor.

Do you see the problem? We want to inherit properties and methods from the Parent constructor by using constructor inheritance, that is, copying a copy into the Child instance object. In this case, the new Parent() is called, so the same properties will be found in child.prototype. Normally, We only use the properties and methods of the prototype when we don’t have them ourselves, but the properties and methods of the prototype are exactly the same as our own. That is, it is not possible to use the properties and methods of the stereotype. So it’s a waste of resources.

Summary of combinatorial inheritance implementation method:

Function Child () {Parent. Call (this,... Arguments)} // Prototype = new ParentCopy the code

✅ advantages:

  1. Can inherit the parent class instance attributes and methods, also can inherit the parent class prototype attributes and methods;
  2. It makes up for the problem of reference attribute sharing in prototype chain inheritance.
  3. Parameter can be transmitted, reusable

❌ faults:

  1. When using composite inheritance, the parent class’s constructor is called twice.
  2. It also generates two instances, and the attributes and methods in the subclass instance overwrite the attributes and methods on the subclass prototype (the superclass instance), thus adding unnecessary memory.

Parasitic combinatorial inheritance

In order to solve the deficiency of combinatorial inheritance, parasitic combinatorial inheritance should be introduced.

How it works: Create a new Object using the object.create () method.

Let’s review the object.create () method first. 🔻

Syntax: Object.create(proto, [propertiesObject]) // The method creates a new Object, using an existing Object to provide the proTO of the newly created Object.

Parameters:

  • Proto: Absolutely. Represents the prototype object of the newly created object, that is, the parameter is assigned to the prototype of the target object (the new object, or the last object returned). This argument can be null, an object, or the function’s prototype property (null is passed when creating an empty object, otherwise TypeError will be raised).

  • PropertiesObject: Optional. The property descriptor and corresponding property name of the enumerable property added to the newly created object (that is, its own property rather than an enumerated property on the stereotype chain). These properties correspond to the second argument to Object.defineProperties().

Return value: The object after a new attribute is added to the specified stereotype object.

function Person(desc){ this.color = ['red']; this.desc = desc; Console.log (' ha ha ')} person.prototype.getName = function(){console.log(this.name); } Child.prototype = Object.create(Person.prototype); function Child(name, age, desc) { this.name = name; this.age = age; Person.call(this,desc); } const Jack = new Child('Jack', 23, 'I'm Jack'); Jack.color.push('pink') const Iric = new Child('Iric', 20, 'Iric'); Iric.color.push('orange') console.log(Jack); Jack.getName(); console.log(Iric); Iric. GetName () / / repair the constructor to Child. The prototype. The constructor = Child;Copy the code

Summary combinatorial inheritance

Prototype = object.create (person.prototype);

✅ advantages:

  1. Public written in the prototype;
  2. Private write in constructor;
  3. Arguments can be passed to the parent class
  4. The parent class is not called repeatedly;

❌ faults:

  1. Need to bind constructor manually (if overriding Prototype)

Conclusion: ES5 inheritance essentially creates an instance of this and then adds the Parent class’s methods to this (parent.apply (this)).

ES5 inheritance can be summarized as follows:

ES6 inheritance

Class can pass the extends keyword implementation inheritance, the by modifying the ES5 prototype chain implementation inheritance, should clear and convenient a lot Mainly use the class extends and super implementation inheritance;

class Person{
  constructor(name, age){
    this.name = name;
    this.age = age;
    this.color = ['red'];
  }
  getName(){
    console.log(this.name);
  }
}

class Child extends Person{
  constructor(name, age){
    super(name, age)    
  }
}

const Jack = new Child('Jack',20);
const Iric = new Child('Iric',23);
Jack.color.push('pink');
Iric.color.push('orange');
Jack.getName();
Iric.getName();
console.log(Jack);
console.log(Iric);
Copy the code

superThe super keyword can be used as either a function or an object. In both cases, it’s used quite differently.

    1. When called as a function, super represents the constructor of the parent class. ES6 requires that the constructor of a subclass must execute the super function once.
class Person {} class Child extends Person { constructor() { super(); // call the parent constructor}}Copy the code

Note that super represents the constructor of the parent class Person, but returns an instance of the subclass Child. This inside super refers to an instance of Child. So super () is equivalent to a Person here. The prototype. The constructor. Call (this).

When used as a function, super() can only be used in the constructor of a subclass, otherwise an error will be reported.

    1. When super is an object, in a normal method, it points to the prototype object of the parent class; In static methods, point to the parent class.

When super is an object, in a normal method, it points to the prototype object of the parent class

class Person{
  p() {
    return 2;
  }
}

class Child extends Person{
  constructor(){
    super();
    console.log(super.p());  //2
  }
}
const c = new Child();
Copy the code

Since super refers to the prototype object of the parent class, methods or properties defined on instances of the parent class cannot be called through super.

In static methods, point to the parent class.

class Person{
  print() {
    console.log('haha');
  }
}

class Child extends Person{
  constructor(){
    super();
    
  }
  getName(){
    super.print();  //haha
  }
}
const c = new Child();
c.getName() 
Copy the code

Note:

  1. In the constructor of a subclass, the this keyword can only be used after super is called, otherwise an error will be reported. This is because subclass instances are built on superclass instances, and only super methods can call superclass instances.
  2. Constructor and super are not necessary to implement inheritance using extends because they are generated and called by default if they are not

ES6 inheritance summary

Core: The essence of ES6 inheritance is to append the properties and methods of the superclass instance object to this (so the super method must be called first), and then modify this using the subclass constructor.

ES6 inheritance can be summarized as follows:

Reference Documents:

  1. MDN inheritance and prototype chain;

  2. MDN object prototype;

  3. Javascript Object-oriented programming (II) : Constructor inheritance