Зарегистрируйтесь, чтобы продолжить обучение

Конструктор JS: Введение в ООП

Приложения на JavaScript во время своей работы создают и удаляют множество объектов. Иногда эти объекты совсем разные, а иногда они относятся к одному понятию, но отличаются данными. Когда речь идет про понятия предметной области (или, как говорят, сущности), то важно иметь абстракцию, которая скроет от нас структуру этого объекта.

Возьмем понятие "компания" и построим абстракцию вокруг него без использования инкапсуляции:

// Реальное устройство будет значительно сложнее
// файл: company.js

// Конструктор (в общем смысле этого слова)
const make = (name, website) => {
  return { name, website }
}

// Селекторы
const getName = company => company.name
const getWebsite = company => company.website

Теперь использование:

import { make, getName } from './company.js'

const company = make('Hexlet', 'https://hexlet.io')
console.log(getName(company)) // Hexlet

Такая абстракция упрощает работу с компаниями (особенно при изменениях структуры), прячет детали реализации и делает код более "человечным". Попробуем сделать то же самое, используя инкапсуляцию:

// Реальное устройство будет значительно сложнее
// файл: company.js

const make = (name, website) => {
  return {
    name,
    website,
    getName() {
      return this.name
    },
    getWebsite() {
      return this.website
    },
  }
}

И использование:

import { make } from './company.js'

const company = make('Hexlet', 'https://hexlet.io')
console.log(company.getName()) // Hexlet

Здесь мы видим несколько удобных моментов по сравнению с вариантом на функциях:

  • Нам больше не надо импортировать getName, он уже содержится внутри компании.
  • Можно пользоваться автодополнением кода (среда разработки сама подскажет все свойства и методы, существующие в company).

Но вместе с плюсами пришли и минусы. Посмотрите еще раз внимательно на код конструктора. Каждый его вызов возвращает новый объект и это ожидаемое поведение, но чего мы точно не хотим, так это создания методов на каждый вызов конструктора (а они будут создаваться при каждом создании объекта). Методы, в отличие от обычных данных, не меняются. Нет никакого смысла создавать их на каждый вызов заново, расходуя память и процессорное время.

Перепишем наш пример, избежав постоянного создания методов:

// Не забываем что нам нужны обычные, а не стрелочные функции!

function getName() {
  return this.name
}

function getWebsite() {
  return this.website
}

// С точки зрения использования ничего не поменялось, но зато перестали копироваться функции.
const make = (name, website) => {
  return {
    name,
    website,
    getName,
    getWebsite,
  }
}

Оператор new

Все описанные выше способы создания объектов имеют право на существование и используются в реальной жизни, но в JavaScript есть встроенная поддержка генерации объектов. Перепишем наш пример с помощью функции-конструктора.

// Такую функцию принято называть конструктором (хотя технически это обычная функция с контекстом)
// Конструкторы пишутся с заглавной буквы
function Company(name, website) {
  this.name = name
  this.website = website
  // Методы по-прежнему определены снаружи как обычные функции
  this.getName = getName
  this.getWebsite = getWebsite
}

Теперь использование:

const company = new Company('Hexlet', 'https://hexlet.io')
console.log(company.getName()) // Hexlet

Самое интересное в этом примере – оператор new (как и многое в js, он работает не так, как new в других языках). Фактически он создает объект, устанавливает его как контекст во время вызова конструктора (в данном случае Company) и возвращает созданный объект. Именно поэтому сам конструктор ничего не возвращает (хотя может, но это другой разговор), а внутри константы company оказывается нужный нам объект.

// Упрощенная иллюстрация работы new внутри интерпретатора при таком вызове:
// new Company();

const obj = {}
Company.bind(obj)(name, website) // этот вызов просто наполнил this (равный obj) нужными данными
return obj

Визуально этот способ выглядит не лучше, чем предыдущее ручное создание, но он задействует еще один важный механизм в JavaScript – прототипы (Подробнее о них в следующем уроке).

Все типы данных в JavaScript, которые могут быть представлены объектами (или являются объектами внутри себя, например, функции), имеют встроенные конструкторы. Иногда они заменяют специальный синтаксис создания данных (как в случае с массивами), а иногда это единственный способ создать данные этого типа (как в случае с датами):

// Специальный синтаксис создания массивов
// Массивы это объекты, вспомните свойство length
const numbers = [10, 3, -3, 0] // литерал

// Объектный способ создания через конструктор
// Результат ниже эквивалентен тому что происходит выше
const numbers = [10, 3, -3, 0]

// У дат нет литералов, они создаются как объекты
const date = new Date('December 17, 1995 03:24:00')
// У дат очень много методов
date.getMonth() // 11, в JS месяцы нумеруются с нуля

// Так можно создавать даже функции
// Последний аргумент это тело, все предыдущие – аргументы
const sum = new Function('a', 'b', 'return a + b')
sum(2, 6) // 8

Но не все функции могут быть конструкторами. Отсутствие своего контекста делает невозможным использование оператора new вместе со стрелочными функциями:

const f = () => {}
// TypeError: function is not a constructor
const obj = new f()

Дополнительные материалы

  1. Natives
  2. Стандартные встроенные объекты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff