Приложения на 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
, он уже содержится внутри компании.Но вместе с плюсами пришли и минусы. Посмотрите еще раз внимательно на код конструктора. Каждый его вызов возвращает новый объект и это ожидаемое поведение, но чего мы точно не хотим, так это создания методов на каждый вызов конструктора (а они будут создаваться при каждом создании объекта). Методы, в отличие от обычных данных, не меняются. Нет никакого смысла создавать их на каждый вызов заново, расходуя память и процессорное время.
Перепишем наш пример, избежав постоянного создания методов:
// Не забываем что нам нужны обычные, а не стрелочные функции!
function getName() {
return this.name;
}
function getWebsite() {
return this.website;
}
// С точки зрения использования ничего не поменялось, но зато перестали копироваться функции.
const make = (name, website) => {
return {
name,
website,
getName,
getWebsite,
};
};
Все описанные выше способы создания объектов имеют право на существование и используются в реальной жизни, но в 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 = new Array(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();
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт