In the previous summary, we analyzed the prototype “JS summary of the prototype” in detail, the prototype is very useful for simulating inheritance, this time, we talk about several ways of prototype inheritance.

🌶 premise

With a parent class as the premise, enumerate the js inheritance method:

function Person (age) {
  this.age = age || 18
}
Person.prototype.sleep = function () {
  console.log('sleeping')}Copy the code

🍖 Approach 1: Prototype chain inheritance (not recommended)

function Programmer() {}

Programmer.prototype = new Person ()
Programmer.prototype.code = function () {
  console.log('coding')}let jon = new Programmer()
jon.code() // coding
jon.sleep() // sleeping

jon instanceof Person // true
jon instanceof Programmer // true

Object.getPrototypeOf(jon) // Person {age: 18, code: ƒ}
jon.__proto__ // Person {age: 18, code: ƒ}
Copy the code

Disadvantages:

  1. Cannot pass a parameter to the parent constructor
  2. All attributes of the parent class are shared, and if one instance modifies an attribute, all other subclass instances are affected

🌭 Approach 2: Borrow constructors (classical inheritance) (not recommended)

Copy the attributes in the parent constructor

function Programmer(name) {
  Person.call(this)
  this.name = name
}
let jon = new Programmer('jon')
jon.name // jon
jon.age / / 18

jon.sleep() // Uncaught TypeError: jon.sleep is not a function
jon instanceof Person // false
jon instanceof Programmer // true
Copy the code

Advantages:

  1. You can pass arguments to a parent class
  2. Shared attributes are avoided

Disadvantages:

  1. It’s an instance of a subclass, not an instance of a superclass
  2. Methods are defined in the constructor and are created each time an instance is created

🍤 Approach 3: Combination Inheritance (recommended)

Composite stereotype chain inheritance and borrowed constructor inheritance.

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

Programmer.prototype = new Person()
Programmer.prototype.constructor = Programmer // Fix constructor pointing

let jon = new Programmer(18.'jon')
jon.age / / 18
jon.name // jon

let flash = new Programmer(22.'flash')
flash.age / / 22
flash.name // flash

jon.age / / 18

jon instanceof Person // true
jon instanceof Programmer // true
flash instanceof Person // true
flash instanceof Programmer // true
Copy the code

Advantages: Combining the advantages of stereotype chain inheritance and constructors is the most common inheritance pattern in JavaScript

Disadvantages: The parent constructor is called twice

🌮 mode 4: Original type inheritance (not recommended)

function create(o) {
  function F() {}
  F.prototype = o
  return new F()
}

let obj = {
  gift: ['a'.'b']}let jon = create(obj)
let xiaoming = create(obj)

jon.gift.push('c')
xiaoming.gift // ['a', 'b', 'c']
Copy the code

Disadvantages: Shared properties and methods

🍳 Approach 5: Parasitic inheritance (not recommended)

Create a function that encapsulates only the inheritance process, internally does the enhancement object in some form, and finally returns the object

function createObj (o) {
  var clone = Object.create(o)
  clone.sayName = function () {
    console.log('hi')}return clone
}
Copy the code

Disadvantages: As with the borrowed constructor pattern, methods are created every time an object is created

🍧 Approach 6: Parasitic combinatorial Inheritance (best)

The subclass constructor copies the parent class’s own attributes and methods, and the subclass stereotype accepts only the parent class’s stereotype attributes and methods:

function create(prototype) {
  function Super() {}
  Super.prototype = prototype
  return new Super()
}

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

Programmer.prototype = create(Person.prototype)
Programmer.prototype.constructor = Programmer // Fix constructor pointing

let jon = new Programmer(18.'jon')
jon.name // jon
Copy the code

Advanced packaging:


function create(prototype) {
  function Super() {}
  Super.prototype = prototype
  return new Super()
}

function prototype(child, parent) {
  let prototype = create(parent.prototype)
  prototype.constructor = child // Fix constructor pointing
  child.prototype = prototype
}

function Person (age) {
  this.age = age || 18
}
Person.prototype.sleep = function () {
  console.log('sleeping')}function Programmer(age, name) {
  Person.call(this, age)
  this.name = name
}

prototype(Programmer, Person)

let jon = new Programmer(18.'jon')
jon.name // jon
Copy the code

To quote the praise of parasitic combinatorial inheritance from JavaScript Advanced Programming:

The efficiency of this approach is that it calls the Parent constructor only once, and thus avoids creating unnecessary, redundant properties on Parent. Prototype. At the same time, the prototype chain stays the same; Therefore, instanceof and isPrototypeOf can also be used normally. Parasitic combinatorial inheritance is generally considered by developers to be the ideal inheritance paradigm for reference types.

🍜 Method 7: ES6 extends (Best)

/ / parent class
class Person {
  constructor(age) {
    this.age = age
  }
  sleep () {
    console.log('sleeping')}}/ / subclass
class Programmer extends Person {
  constructor(age, name) {
    super(age)
    this.name = name
  }
  code () {
    console.log('coding')}}let jon = new Programmer(18.'jon')
jon.name // jon
jon.age / / 18

let flash = new Programmer(22.'flash')
flash.age / / 22
flash.name // flash

jon instanceof Person // true
jon instanceof Programmer // true
flash instanceof Person // true
flash instanceof Programmer // true
Copy the code

Advantages: No manual prototyping.

Disadvantages: New syntax, as long as some browsers support it, needs to be converted to ES5 code.

🚀 reference

  • “JavaScript Advanced Programming (3rd edition)” 6.3 Inheritance
  • “Multiple ways of JavaScript Deep Inheritance and Pros and Cons” by Hu Yu
  • Introduction to ECMAScript 6 Class inheritance by Ruan Yifeng