https://habrahabr.ru/company/raiffeisenbank/blog/340584/- JavaScript
- Блог компании Райффайзенбанк
Этот перевод — для новичков, делающих первые шаги в JavaScript, или даже в программировании вообще.
JavaScript — мощный объектно-ориентированный (ООП) язык. Но, в отличие от многих других языков, он использует ООП-модель на основе прототипов, что делает его синтаксис непривычным для многих разработчиков. Кроме того, JavaScript работает с функциями как с объектами первого класса, что может путать программистов, не знакомых с этими концепциями. Можно обойти их, применяя производный язык вроде TypeScript, имеющий знакомый синтаксис и предлагающий дополнительные возможности. Но такие языки всё-равно компилируются в чистый JavaScript, и простое знание об этом не поможет вам понять, как они работают на самом деле, а также когда целесообразно их применять.
О чём мы поговорим в этой статье:
- Пространство имён.
- Объекты.
- Объектные литералы.
- Функции-конструкторы.
- Наследование.
Пространство имён
В сети появляется всё больше сторонних библиотек, фреймворков и зависимостей, поэтому определение пространства имён является необходимостью в JavaScript-разработке, если мы хотим избежать коллизий между объектами и переменными в глобальном пространстве имён.
К сожалению, JS не имеет встроенной поддержки определения пространства имён, но мы можем использовать объекты для достижения того же результата. Есть много разных паттернов для реализации, но мы рассмотрим только самый распространённый — вложенные пространства имён.
Этот паттерн использует объектный литерал, чтобы собрать функциональность в пакет и дать ему уникальное, специфичное для приложения имя. Можем начать с создания глобального объекта и присвоения его к переменной:
var MyApp = MyApp || {};
По той же методике можно создавать подпространства имён:
MyApp.users = MyApp.user || {};
Сделав контейнер, мы можем использовать его для определения методов и свойств, а затем применять их в нашем глобальном пространстве имён без риска возникновения коллизий с существующими дефинициями.
MyApp.users = {
// свойства
existingUsers: [...],
// методы
renderUsersHTML: function() {
...
}
};
Подробнее о паттернах определения пространств имён в JavaScript можно почитать здесь:
Essential JavaScript Namespacing Patterns.Объекты
Если вы уже писали код на JavaScript, то в той или иной мере использовали объекты. JavaScript имеет три различных типа объектов:
Нативные объекты (Native Objects)
Нативные объекты — часть спецификации языка. Они доступны нам вне зависимости от того, на каком клиенте исполняется наш код. Примеры: Array, Date и Math.
Полный список нативных объектов.
var users = Array(); // Array — нативный объект
Хост-объекты (Host Objects)
В отличие от нативных, хост-объекты становятся нам доступны благодаря клиентам, на которых исполняется наш код. На разных клиентах мы в большинстве случаев можем взаимодействовать с разными хост-объектами. Например, если пишем код для браузера, то он предоставляет нам window, document, location и history.
document.body.innerHTML = 'Hello'; // document — это хост-объект
Пользовательские объекты (User Objects)
Пользовательские объекты, иногда называемые предоставленными (contributed objects), — наши собственные объекты, определяемые в ходе run time. Есть два способа объявления своих объектов в JS, и мы рассмотрим их далее.
Объектные литералы (Object Literals)
Мы уже коснулись объектных литералов в главе про определение пространства имён. Теперь поясним: объектный литерал — это разделённый запятыми список пар имя-значение, помещённый в фигурные скобки. Эти литералы могут содержать свойства и методы, и как и любые другие объекты в JS могут передаваться функциям и возвращаться ими. Пример объектного литерала:
var dog = {
// свойства
breed: ‘Bulldog’,
// методы
bark: function() {
console.log(“Woof!”);
},
};
// обращение к свойствам и методам
dog.bark();
Объектные литералы являются синглтонами. Чаще всего их используют для инкапсулирования кода и заключения его в аккуратный пакет во избежание коллизий с переменными и объектами в глобальной области видимости (пространстве имён), а также для передачи конфигураций в плагины и объекты.
Объектные литералы полезны, но не могут быть инстанцированы и от них нельзя наследовать. Если вам нужны эти возможности, то придётся обратиться к другому методу создания объектов в JS.
Функции-конструкторы
В JavaScript функции считаются объектами первого класса, то есть они поддерживают те же операции, что доступны для других сущностей. В реалиях языка это означает, что функции могут быть сконструированы в ходе run time, переданы в качестве аргументов, возвращены из других функций и присвоены переменным. Более того, они могут иметь собственные свойства и методы. Это позволяет использовать функции как объекты, которые могут быть инстанцированы и от которых можно наследовать.
Пример использования определения объекта с помощью функции-конструктора:
function User( name, email ) {
// свойства
this.name = name;
this.email = email;
// методы
this.sayHey = function() {
console.log( “Hey, I’m “ + this.name );
};
}
// инстанцирование объекта
var steve = new User( “Steve”, “steve@hotmail.com” );
// обращение к методам и свойствам
steve.sayHey();
Создание функции-конструктора аналогично созданию регулярного выражения за одним исключением: мы используем ключевое слово this для объявления свойств и методов.
Инстанцирование функций-конструкторов с помощью ключевого слова new аналогично инстанцированию объекта в традиционном языке программирования, основанном на классах. Однако здесь есть одна неочевидная, на первый взгляд, проблема.
При создании в JS новых объектов с помощью ключевого слова new мы раз за разом выполняем функциональный блок (function block), что заставляет наш скрипт КАЖДЫЙ РАЗ объявлять анонимные функции для каждого метода. В результате программа потребляет больше памяти, чем следует, что может серьёзно повлиять на производительность, в зависимости от масштабов программы.
К счастью, есть способ прикрепления методов к функциям-конструкторам без загрязнения глобальной области видимости.
Методы и прототипы
JavaScript — прототипичный (prototypal) язык, то есть мы можем использовать прототпы в качестве шаблонов объектов. Это поможет нам избежать ловушки с анонимными функциями по мере масштабирования наших приложений. Prototype — специальное свойство в JavaScript, позволяющее добавлять к объектам новые методы.
Вот вариант нашего примера, переписанный с использованием прототипов:
function User( name, email ) {
// свойства
this.name = name;
this.email = email;
}
// методы
User.prototype.sayHey = function() {
console.log( “Hey, I’m “ + this.name );
}
// инстанцирование объекта
var steve = new User( “Steve”, “steve@hotmail.com” );
// обращение к методам и свойствам
steve.sayHey();
В этом примере
sayHey()
будет совместно использоваться всеми экземплярами объекта User.
Наследование
Также прототипы используются для наследования в рамках цепочки прототипов. В JS каждый объект имеет прототип, а раз прототип — всего лишь ещё один объект, то и у него тоже есть прототип, и так далее… пока не дойдём до прототипа со значением
null
— это последнее звено цепочки.
Когда мы обращаемся к методу или свойству, JS проверяет, задан ли он в определении объекта, и если нет, то проверяет прототип и ищет определение там. Если и в прототипе не находит, то идёт по цепочке прототипов, пока не найдёт или пока не достигнет конца цепочки.
Вот как это работает:
// пользовательский объект
function User( name, email, role ) {
this.name = name;
this.email = email;
this.role = role;
}
User.prototype.sayHey = function() {
console.log( “Hey, I’m an “ + role);
}
// объект editor наследует от user
function Editor( name, email ) {
// функция Call вызывает Constructor или User и наделяет Editor теми же свойствами
User.call(this, name, email, "admin");
}
// Для настройки цепочки прототипов мы с помощью прототипа User создадим новый объект и присвоим его прототипу Editor
Editor.prototype = Object.create( User.prototype );
// Теперь из объекта Editor можно обращаться ко всем свойствам и методам User
var david = new Editor( "David", "matthew@medium.com" );
david.sayHey();
У вас может уйти какое-то время на привыкание к прототипичному наследованию, но важно освоить эту концепцию, если вы хотите достигнуть высот в ванильном JavaScript. Хотя её часто называют одним из слабых мест языка, прототипичная модель наследования фактически мощнее классической модели. Например, не составит труда построить классическую модель поверх прототипичной.
В ECMAScript 6 появился новый набор ключевых слов, реализующих классы. Хотя эти конструкты выглядят так же, как в основанных на классах языках, это не одно и то же. JavaScript по прежнему основан на прототипах.
* * *
JavaScript развивался долгое время, в течение которого в него внедрялись разные практики, которых по современным меркам нужно избегать. С появлением ES2015 ситуация начала медленно меняться, но всё же многие разработчики придерживаются всё ещё придерживаются старых методов, которые ставят под угрозу релевантность их кода. Понимание и применение в JavaScript ООП-концепций критически важно для написания устойчивого кода, и я надеюсь, что это краткое введение поможет вам в этом.