Six inheritance methods in JS and their advantages and disadvantages

Six inheritance methods in JS and their advantages and disadvantages

Preface

Inheritance is an indispensable part of the JS world, known as one of the three mountains of JS. Using this method, we can better reuse the previous development code, shorten the development cycle, and improve development efficiency.

Before ES6, classes in JS were simulated by constructors, and there were no real classes. Although the class in ES6 is a syntax sugar, classes in this period can be used directly as functions. After ES6, classes can no longer be used as functions.

Before we start talking about inheritance, we first need to make it clear that there are two types of attributes in a class: instance attributes and public attributes. All the inheritance methods discussed below will revolve around these two points.

function Animal(name) {
  // Attribute on the instance this.name = name;
}

// Public properties Animal.prototype.eat = function() {
  // todo ...
}

How to avoid calling pre-ES6 constructors directly as functions?

ES5 solution:

function Animal() {
    // If called directly, not using new, throw an exception if (!(this instanceof Animal)) {
        // The principle of new. When using new, this is an instance of Animal, then this instanceof Animal is true
        throw new Error("Do not call the constructor directly");
    }
}

ES6 and later solutions:

function Animal() {
    // If new is used, new.target points to itself, otherwise it is undefined. However, it cannot be used when inheriting, because when inheriting properties on an instance, the original es5 uses Animal.call(this) if (!new.target) {
        throw new Error("Do not call the constructor directly");
    }
}

Both of the above solutions can be solved by calling them directly as functions. If you call the console directly, an error will be reported: Uncaught Error: Do not call the constructor directly

Next, let’s take a look at the inheritance methods in JS

Prototype chain inheritance

Prototype chain inheritance is one of the more common inheritance methods. There is a certain relationship between the constructor, prototype and instance involved, that is, each constructor has a prototype object, the prototype object contains a pointer to the constructor, and the instance contains a pointer to the prototype object.

function Person(name) {
    this.name = name;
    this.permission = ["user", "salary", "vacation"];
}

Person.prototype.say = function () {
    console.log(`${this.name} spoke`);
};

function Staff(age) {
    this.age = age;
}

Staff.prototype = new Person("Zhang San");

const zs = new Staff(12);
console.log(zs.name); // Zhang Sanzs.say(); // Zhang San spoke

At this point the code is in line with expectations. Next, create an instance and modify the name and permission.

const zs = new Staff(12);
const zs2 = new Staff(18);
zs.permission.pop()
zs.name = 'Li Si';

console.log(zs.name);
console.log(zs2.name);
console.log(zs.permission);
console.log(zs2.permission);

The outputs of the first two are: Li Si and Zhang San, while the outputs of the last two are the same, both ["user", "salary"]. Why does this happen?
When zs.name = '李四'; is executed, it is actually an assignment operation. After the assignment, zs becomes

zs2.name continues to search through the prototype chain, so the first two outputs are Li Si and Zhang San

By outputting true through console.log(zs.__proto__ === zs2.__proto__);, we can know that the two instances use the same prototype object Person, and their memory space is shared. When one changes, the other also changes accordingly.

Through the above, we find that prototype chain inheritance has some disadvantages

Constructor inheritance

Constructors usually use call and apply to complete inheritance

function Person(name) {
    this.name = name;
    this.permission = ["user", "salary", "vacation"];
}

Person.prototype.say = function () {
    console.log(`${this.name} spoke`);
};

function Staff(name, age) {
    Person.call(this, name);
    this.age = age;
}

Staff.prototype.eat = function () {
    console.log('Let's eat~~~~');
}

const zs = new Staff("张三", 12);
console.log(zs);

The above code console output:

It can be seen that it not only has the properties and methods of Staff, but also inherits the properties of Person, because Person.call(this, name); is called every time it is instantiated, which can solve the problem of prototype chain inheritance.

At this time, call the method on the Person prototype

zs.say()

At this time, the console will report an error: Uncaught TypeError: zs.say is not a function

Combination inheritance (prototype chain inheritance and constructor inheritance combination)

Prototype chain inheritance and constructor inheritance both have their own problems and advantages. Combining the two inheritance methods generates composite inheritance.

function Person(name) {
    this.name = name;
    this.permission = ["user", "salary", "vacation"];
}

Person.prototype.say = function () {
	console.log(`${this.name} spoke`);
};

function Staff(name, age) {
    // Second execution of Person
    Person.call(this, name);
    this.age = age;
}

Staff.prototype.eat = function () {
    console.log("Let's eat~~~~");
};

// First execution of Person
Staff.prototype = new Person();
// If you do not point the Staff constructor back to Staff, the Staff instance zs.constructor will point to Person
Staff.prototype.constructor = Staff;

const zs = new Staff("张三", 12);
const ls = new Staff("Li Si", 12);
zs.permission.pop();
console.log(zs.permission);
console.log(ls.permission);
zs.say();
ls.say();

For the time being, the console output is normal, and the above two inheritance shortcomings are solved, but two new problems arise:

  1. Person is executed twice: Person.call(this, name) and new Person(). It is expected to be executed once. The extra execution will cause performance overhead.
  2. When Staff.prototype = new Person() defines some public properties and methods, they will be overwritten. For example, you cannot call zs.eat() on an instance, and the console will report an error Uncaught TypeError: zs.eat is not a function. If you define it after that, it will pollute Person.

Parasitic inheritance

By using Object.create to obtain a shallow copy of the target object, and then adding some methods to avoid polluting the base class, it mainly solves the second problem of composite inheritance.

Mainly replace the following two lines of code

Staff.prototype = new Person();
Staff.prototype.constructor = Staff;

Replace with:

Staff.prototype = Object.create(Person.prototype, {
    constructor: {
        // If you do not point the Staff constructor back to Staff, the Staff instance zs.constructor will point to Person
        value: Staff,
    },
});

Combinatorial parasitic inheritance

So far, there is still a problem of instantiating Person twice that has not been solved. The following combined parasitic inheritance can perfectly solve the above problem. This is also the best inheritance method among all inheritance methods before ES6.

The complete code is as follows:

function Person(name) {
    this.name = name;
    this.permission = ["user", "salary", "vacation"];
}

Person.prototype.say = function () {
    console.log(`${this.name} spoke`);
};

function Staff(name, age) {
    Person.call(this, name);
    this.age = age;
}

Staff.prototype = Object.create(Person.prototype, {
    constructor: {
        // If you do not point the Staff constructor back to Staff, the Staff instance zs.constructor will point to Person
        value: Staff,
    },
});

Staff.prototype.eat = function () {
    console.log("Let's eat~~~~");
};

In fact, when inheriting, there are more ways to change Staff.prototype to point to than just the above, there are also some other methods

  • prototype.__proto__ method
Staff.prototype.__proto__ = Person.prototype

There is a compatibility issue with prototype.__proto__. It cannot be found by itself. It continues to search upward through the prototype chain. At this time, Animal and Tiger will no longer share the same address and will not affect each other.

  • Object.setPrototypeOf Method
Object.setPrototypeOf(Staff.prototype, Person.prototype)

es6 syntax, there is compatibility, the principle is the prototype.__proto__ method

extends

After ES6, you can use extends for inheritance, which is also the most commonly used method in current development. Although the browser support is not ideal, with the improvement of engineering today, these are no longer reasons to restrict its use.

class Person {
    constructor(name) {
        this.name = name;
        this.permission = ["user", "salary", "vacation"];
    }

    say() {
        console.log(`${this.name} spoke`);
    }
}

class Staff extends Person {
    constructor(name, age) {
        super(name);
        this.age = age;
    }

    eat() {
        console.log("Let's eat~~~~");
    }
}

In fact, after ES6 inheritance is compiled by babel, it also adopts combined parasitic inheritance, so we need to focus on mastering its inheritance principle.

Summarize

This concludes this article about the six inheritance methods and their advantages and disadvantages in JS. For more information about JS inheritance methods and their advantages and disadvantages, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Understanding JavaScript inheritance in one article
  • Summary of 6 common inheritance methods in js
  • 6 inheritance methods of JS advanced ES6
  • Detailed explanation of native Javascript inheritance methods and their advantages and disadvantages
  • Detailed explanation of 6 ways of js inheritance
  • Several ways to implement inheritance in JavaScript
  • 6 JavaScript inheritance methods and their advantages and disadvantages (summary)
  • Examples of several common ways to implement inheritance in JS
  • Share several inheritance methods in JavaScript

<<:  Solve the problem of running hello-world after docker installation

>>:  How to remotely connect to MySQL database with Navicat Premium

Recommend

HTML uncommon tags optgroup, sub, sup and bdo example code

Optgroup is used in the select tag to make the dro...

Mobile browser Viewport parameters (web front-end design)

Mobile browsers place web pages in a virtual "...

The best 9 foreign free picture material websites

It is difficult to find good image material websi...

js learning notes: class, super and extends keywords

Table of contents Preface 1. Create objects befor...

MySQL 8.0.21 free installation version configuration method graphic tutorial

Six steps to install MySQL (only the installation...

Web front-end development course What are the web front-end development tools

With the development of Internet technology, user...

Detailed explanation of CSS3 animation and new features of HTML5

1. CSS3 animation ☺CSS3 animations are much easie...

Solution to the MySQL error "Every derived table must have its own alias"

MySQL reports an error when executing multi-table...

uniapp Sample code for implementing global sharing of WeChat mini-programs

Table of contents Create a global shared content ...

A detailed introduction to JavaScript execution mechanism

Table of contents 1. The concept of process and t...

Common naming rules for CSS classes and ids

Public name of the page: #wrapper - - The outer e...

Solution to 1067 when Mysql starts in Windows

I just started working a few days ago and install...

A brief discussion on the mysql execution process and sequence

Table of contents 1:mysql execution process 1.1: ...

Tips for Mixing OR and AND in SQL Statements

Today, there is such a requirement. If the logged...