Несколько способов прототипного наследования, обобщенные JS

JavaScript
Несколько способов прототипного наследования, обобщенные JS

В предыдущем резюме мы подробно разобрали прототип«Прототип JS Резюме», Прототип очень полезен для моделирования наследования, на этот раз давайте поговорим о нескольких способах прототипного наследования.

🌶 Предпосылки

Взяв родительский класс в качестве предварительного условия, перечислите методы наследования js:

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

🍖 Метод 1: наследование цепочки прототипов (не рекомендуется)

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: ƒ}

недостаток:

  1. Невозможно передать аргументы конструктору родительского класса
  2. Все свойства родительского класса являются общими, пока один экземпляр изменяет свойство, все остальные экземпляры подкласса будут затронуты.

🌭 Подход 2: заимствование конструкторов (классическое наследование) (устарело)

Копировать родительский классСвойства внутри конструктора

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

преимущество:

  1. Может передавать параметры для родительского класса
  2. избегать общих свойств

недостаток:

  1. Просто экземпляр подкласса, а не родительского класса
  2. Методы определяются в конструкторе, и метод создается каждый раз при создании экземпляра.

🍤🍤 3: Комбинированное наследование (рекомендуется)

комбинацияНаследование цепочки прототипова такжеЗаимствование наследования конструктора.

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

Programmer.prototype = new Person()
Programmer.prototype.constructor = Programmer // 修复构造函数指向

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

Преимущества: сочетает в себе преимущества наследования цепочки прототипов и конструкторов и является наиболее часто используемым шаблоном наследования в JavaScript.

Недостаток: конструктор родительского класса вызывается дважды.

🌮 Подход 4: прототипное наследование (не рекомендуется)

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']

Недостаток: общие свойства и методы.

🍳 Подход 5: Паразитическое наследование (не рекомендуется)

Создайте функцию, которая используется только для инкапсуляции процесса наследования, функция внутренне улучшает объект в той или иной форме и, наконец, возвращает объект

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

Недостаток: подобно заимствованию шаблона конструктора, каждый раз, когда вы создаете объект, вы создаете метод.

🍧 Подход 6: Наследование паразитарной композиции (лучшее)

Конструктор подкласса копирует собственные свойства и методы родительского класса, а прототип подкласса принимает только свойства и методы прототипа родительского класса:

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 // 修复构造函数指向

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

Расширенная упаковка:


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

function prototype(child, parent) {
  let prototype = create(parent.prototype)
  prototype.constructor = child // 修复构造函数指向
  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

Чтобы процитировать похвалу паразитическому композиционному наследованию в Advanced JavaScript Programming:

Эффективность этого подхода заключается в том, что он вызывает конструктор Parent только один раз и, таким образом, позволяет избежать создания ненужных избыточных свойств в Parent.prototype. При этом цепочка прототипов остается неизменной, поэтому можно нормально использовать instanceof и isPrototypeOf. Разработчики в целом согласны с тем, что паразитное композиционное наследование является идеальной парадигмой наследования для ссылочных типов.

🍜 Способ 7: расширение ES6 (лучшее)

// 父类
class Person {
  constructor(age) {
    this.age = age
  }
  sleep () {
    console.log('sleeping')
  }
}

// 子类
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

Плюсы: нет необходимости вручную настраивать прототипы.

Недостаток: новый синтаксис, если его поддерживают некоторые браузеры, необходимо преобразовать в код ES5.

🚀 ссылка