ДокументацияJavaScriptПрототипы и наследование
Средний 9 мин чтения

Прототипы и наследование

JavaScript использует прототипное наследование — объекты наследуют свойства и методы через цепочку прототипов.

prototypeinheritanceclassOOP

Прототипная цепочка

В JavaScript каждый объект имеет скрытое свойство [[Prototype]] — ссылку на другой объект (прототип). При обращении к свойству JavaScript сначала ищет его в самом объекте, затем поднимается по цепочке прототипов.

const animal = {
  greet() {
    return `Я ${this.name}`
  },
}

const dog = {
  name: 'Рекс',
  bark() {
    return 'Гав!'
  },
}

// Устанавливаем прототип
Object.setPrototypeOf(dog, animal)

console.log(dog.bark())   // 'Гав!' — собственный метод
console.log(dog.greet())  // 'Я Рекс' — из прототипа

__proto__ и Object.getPrototypeOf

console.log(Object.getPrototypeOf(dog) === animal) // true

// __proto__ — устаревший способ, лучше не использовать
console.log(dog.__proto__ === animal) // true (но __proto__ deprecated)

Функции-конструкторы

До ES6 объекты создавались через функции-конструкторы:

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

// Методы добавляются в prototype, не в каждый экземпляр
Person.prototype.greet = function () {
  return `Привет, я ${this.name}`
}

const ivan = new Person('Иван', 25)
const maria = new Person('Мария', 30)

console.log(ivan.greet())   // 'Привет, я Иван'
console.log(maria.greet())  // 'Привет, я Мария'

// Оба используют один и тот же метод greet из прототипа
console.log(ivan.greet === maria.greet) // true

Классы (ES6+)

Классы — синтаксический сахар над прототипным наследованием:

class Animal {
  constructor(name) {
    this.name = name
  }

  speak() {
    return `${this.name} издаёт звук`
  }

  toString() {
    return `Animal(${this.name})`
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name)  // вызов конструктора родителя
    this.breed = breed
  }

  speak() {
    return `${this.name} лает: Гав!`
  }
}

const rex = new Dog('Рекс', 'Немецкая овчарка')
console.log(rex.speak())    // 'Рекс лает: Гав!'
console.log(rex.toString()) // 'Animal(Рекс)' — из родителя

Проверка наследования

console.log(rex instanceof Dog)    // true
console.log(rex instanceof Animal) // true

// Проверить наличие собственного свойства
console.log(rex.hasOwnProperty('name'))  // true
console.log(rex.hasOwnProperty('speak')) // false — метод в прототипе

// Получить только собственные свойства
const ownProps = Object.keys(rex) // ['name', 'breed']

Статические методы и свойства

class MathUtils {
  static PI = 3.14159

  static circle(r) {
    return MathUtils.PI * r * r
  }
}

// Вызываются на классе, не на экземпляре
console.log(MathUtils.PI)          // 3.14159
console.log(MathUtils.circle(5))   // 78.539...

Приватные поля (ES2022)

class BankAccount {
  #balance  // приватное поле

  constructor(initial) {
    this.#balance = initial
  }

  deposit(amount) {
    this.#balance += amount
  }

  get balance() {
    return this.#balance
  }
}

const acc = new BankAccount(1000)
acc.deposit(500)
console.log(acc.balance)   // 1500
// acc.#balance → SyntaxError

Миксины

JavaScript не поддерживает множественное наследование, но можно использовать миксины:

const Serializable = (Base) =>
  class extends Base {
    serialize() {
      return JSON.stringify(this)
    }

    static deserialize(json) {
      return Object.assign(new this(), JSON.parse(json))
    }
  }

const Validatable = (Base) =>
  class extends Base {
    validate() {
      return Object.values(this).every(Boolean)
    }
  }

class User extends Serializable(Validatable(class {})) {
  constructor(name, email) {
    super()
    this.name = name
    this.email = email
  }
}

const user = new User('Иван', 'ivan@example.com')
console.log(user.validate())  // true
console.log(user.serialize()) // '{"name":"Иван","email":"ivan@example.com"}'

Итог

  • JavaScript использует прототипное наследование
  • class — синтаксический сахар, под капотом те же прототипы
  • Методы лучше хранить в prototype, а не в конструкторе (экономия памяти)
  • instanceof проверяет всю цепочку прототипов
  • Приватные поля #field — настоящая инкапсуляция (ES2022)