Классы в ES6
Классы являются основой объектно-ориентированное программирование но не в JavaScript.
Все материалы по ES6
В объектно-ориентированное программирование наследование основано на прототипах объектов, а не на классах.
Если вы когда-нибудь писали код на Java Script имитируя классы, ты уверен согласитесь синтаксис не самый элегантный и простой.
С появлением es6 JavaScript наконец-то добавлена поддержка классов. Новый синтаксис не вводит новую объектно-ориентированная модель наследования, внутри используется всё те же прототипы.
Синтаксис классов это так называемый syntactic Sugar или синтаксический сахар, который призван немного подсластить, то есть упростить работу с классами и наследованием в JavaScript.
Сегодня мы рассмотрим новый синтаксис классов, а в следующем посте - наследование.
Первое, что мы сделаем, это запустим скрипт Babel, который будет следить за файлами и при их изменении создавать свежие версии.
Откроем папку проекта в командной строке (КС). Вводим команду:
npm run watch
И нажать Enter
В папке src создадим файл classes.js и сразу укажу его в файле index.html
<script src="src/classes.js"></script>
HTML последние версии браузеров поддерживает класс без транспиляции, и мой браузер входит в их число, но тем не менее в конце, мы посмотрим на транспилированный код.
Начнем сначала а именно создание классов.
Создание классов
Класс это формы или шаблон по которому создаются объекты. Классы в JavaScript определяют свойства, то есть какой будет объект и методы - что этот объект будет делать. При создание классов используется ключевое словоclass
,
после которого указывается имя класса. В качестве примера я создам класс названием Task
- или задача.
Тело класса находится в фигурных скобках и в нём будут находиться члены класса, методы и свойства.
А в консоли мы посмотрим на класс используя оператор typeof
.
class Task { }; console.log( typeof Task );
В консоли мы видим
function
. Это означает, что классы это функции,
которые создают объекты.
Теперь перейдем к созданию объектов.
Объекты
Объект - это экземпляр или представитель класса.То есть объект созданный по шаблону с определёнными свойствами и методами указанными в классе. Для создания объектов мы используем ключевое слово
new
.
После класса мы объявим переменную
task
с маленькой буквы и в качестве значения укажу new Task
То есть мы вызываем функцию Task
передний ней ключевое слово - new
.
Теперь, давайте в консоли посмотрим на тип переменной
task
и также проверим является ли task
,
представителем класса Task
-с большой буквы.
Мы можем это проверить используя оператор
instanceof
.
class Task { }; let task = new Task(); console.log( typeof task ); console.log( task instanceof Task );
Мы видим, что переменная тоски является объектом или точнее, содержит ссылку на объект, и этот объект является представителем класса.
Конструктор.
Конструктор - это особый метод, который вызывается в момент создания объекта.То есть когда мы используем ключевое слово
new
Он создаёт свойства и инициализирует.
То есть подготавливает объект к использованию.
В классе может быть только один конструктор!
Если мы укажем несколько, то получим ошибку.
Если же мне укажем конструктор вообще-то JavaScript создаст пустой конструктор.
Для того чтобы объявить конструктор в теле класса пишем
constructor() {}
Внутри конструктора я выведу сообщение о том что происходит создание задачи.
class Task {
constructor() {
console.log('Происходит создание задачи');
}
};
let task = new Task();
console.log( typeof task );
console.log( task instanceof Task );
Давайте посмотрим на результат браузере.
и мы увидим сообщение о создании задачи
тем самым мы увидели, что конструктор сработал.
Свойства
Свойства - это характеристики объекта.Они описывают какой объект или что у него имеется.
Свойство указывается в конструкторе. Таким образом каждый представитель класса имеет эти свойства.
Для того чтобы создать свойства у объекта, в конструкторе используется ключевое слово
this
,
которое указывает на объект, который мы создаем.
Например у класса
Task
будет свойства title
или заголовок,
который будет содержать заголовок задачи.
Для того, чтобы указать свойство я напишу слово
this
.
Дальше названия свойства title
и в принципе всё,
но мы можем указать значение. И в качестве значения давайте укажем
"Выучить JavaScript"
class Task { constructor() { this.title = "Выучить JavaScript"; console.log('Происходит создание задачи'); } }; let task = new Task(); console.log( typeof task ); console.log( task.title );
и в консоли давайте выведем значение
title
.
Конструктор может принимать параметры, которые мы можем указать в качестве аргументов при создании нового объекта. Давайте скопируем название задачи из конструктора и вставим его между круглыми скобками.
let task = new Task("Выучить JavaScript");
Далее, в конструкторе нам необходимо указать параметр
title
.
И давайте присвоим ему значение по умолчанию пустую строку.
И свойству при присвоении - значение параметра
title
.
class Task { constructor( title = '' ) { this.title = title; console.log('Происходит создание задачи'); } }; let task = new Task("Выучить JavaScript"); console.log( typeof task ); console.log( task.title );
И в консоли мы видим тот же самый результат.
Теперь мы можем создать вторую задачу и в качестве заголовка указать что-нибудь другое - номер "выучить ES6".
class Task { constructor( title = '' ) { this.title = title; console.log('Происходит создание задачи'); } }; let task = new Task("Выучить JavaScript"); let task2 = new Task("Выучить ES6"); console.log( typeof task ); console.log( task.title ); console.log( task2.title );
Давайте добавим классу
Task
ещё одно свойство которое будет называться done
,
в котором будет указываться выполнена задача или нет.
По умолчанию мы поставим
false
. То есть задачи по умолчанию будет не выполнена.
При создании объекта принимать аргумент
done
мы не будем.
Одно маленькое замечание - свойства указывается только в конструкторе!
Если у вас есть опыт работы с другими языками и такими как Java, C++ или C#
в которых свойство указывает отдельно от конструктора, а в конструкторе инициализируется, то в джаваскрипт
свойство указываются исключительно в конструкторе!
Так как на фото выше мы сделать не можем!
Или убрать слово let и var или const всё это не будет работать!
Насколько я знаю это исправят в следующей версии JvaScript.
Далее мы рассмотрим создание методов методы.
Методы.
Методы это то что объект умеет делать, то есть его возможности.По сути это функции, которые в классе называются методами.
Давайте добавим классу
Task
метод complete(){}
,
с помощью которого мы укажем что задача выполнена.
Чтобы указать метод мы пишем названия метода, после чего пара круглых скобок за ними пару фигурных скобок. В теле метода мы напишем
this.done = true;
Заметьте что между методами в классах не ставится, если я поставлю то мы получим ошибку
И для того чтобы видеть результат -
console.log(`Задача "${this.title}" выполнена!`);
class Task { constructor( title = '' ) { this.title = title; this.done = false; console.log('Происходит создание задачи'); } complete() { this.done = true; console.log(`Задача "${this.title}" выполнена!`); } }; let task = new Task("Выучить JavaScript"); let task2 = new Task("Выучить ES6"); console.log( typeof task ); console.log( task.title ); console.log( task2.title ); task2.complete();
В самом сообщении воспользуюсь шаблоны строкой.
Далее, придём к статическим свойствам и методам
Статические свойства и методы
Статические свойства.
Статические свойства - это свойства которые принадлежат самому классу, а не объектам, созданным на его основе.
Статические свойства часто используются для хранения вспомогательной информации и в отличие от многих других языков, например Java, C++ или C#, мы не можем Объявлять статические свойства используют ключевое слово
static
.
Если мы попробую написать будет ошибка.
Так что же делать если статическая переменная мы не можем объявить о самом классе?
В самом деле это делается очень просто.
После объявления класса, мы пишем название класса и далее, так как функции в JavaScript являются такими же объектами, как и всё остальное, мы можем просто присвоить свойство.
И сразу укажем значение.
И в конструкторе при создании задачи мы давали
Task.count += 1;
И после того как мы создали задачу...
class Task { constructor( title = '' ) { this.title = title; this.done = false; Task.count += 1; console.log('Происходит создание задачи'); } complete() { this.done = true; console.log(`Задача "${this.title}" выполнена!`); } }; Task.count = 0; let task = new Task("Выучить JavaScript"); let task2 = new Task("Выучить ES6"); console.log( task.title ); console.log( task2.title ); console.log( Task.count ); task2.complete();
Давайте вывезем значение в консоль. Оно должно равняться двум.
И действительно, мы видим
2
.
Статические методы.
Статические методы это методы которые принадлежат самому классу, а не объектам созданным на его основе.Статические методы часто используются для создания вспомогательных функций.
Давайте добавим Task статический метод.
Для того чтобы создать статический метод убежим ключевое слово
static
и дальше название метода.
Статический метод
getDefaultTitle()
с помощью которого мы сможем получить значение заголовка по умолчанию если тот не был передан.
Поэтому в теле метода мы просто вернем слово "Задача"
.
static getDefaultTitle() { return "Задача"; }
теперь в конструкторе мы можем указать ->
class Task { constructor( title = Task.getDefaultTitle() ) { this.title = title; this.done = false; Task.count += 1; console.log('Происходит создание задачи'); } get done() { return this.done === true ? 'Выполнено' : 'Не выполнено'; } complete() { this.done = true; console.log(`Задача "${this.title}" выполнена!`); } static getDefaultTitle() { return "Задача"; } }; Task.count = 0; let task = new Task("Выучить JavaScript"); let task2 = new Task("Выучить ES6"); let task3 = new Task(); console.log( task.title ); console.log( task2.title ); console.log( task3.title ); console.log( Task.count ); task2.complete();
Давайте создадим задачу 3 без аргументов посмотрим на результат.
И мы видим что заголовок задачи 3 просто задача.
Если мы попытаемся вызвать метод .getDefaultTitle() у образца объекта которые мы сохранили в переменной task или task2 - не важно, то мы получим ошибку!
Давайте попробуем так -
task.getDefaultTitle();
Мы получим ошибку Uncaught TypeError: task.getDefaultTitle is not a function.То есть у объектов task этого метода нет!
Единственный способ получить к нему доступ - использовать название самого класса!
У классов, помимо свойство методов можно добавить особые свойства get и set.
Особые свойства get и set.
Особые свойства get и set еще называются геттер и сеттер которые внутри класса выглядит ведут себя как методы а снаружи выглядит и ведут себя как свойства.эти свойства - методы позволяют получить доступ и присвоить значение "настоящим свойствам объекта". То есть выступать в роли фильтра.
Давайте добавим свойства get и set классу - Task.
Для начала я немного почищу код. Удалю все созданные объекты и их выводы, кроме одного - task.
Итак начнем.
Get или гетер.
Если вывести в консоль значения console.log(task.done); то мы увидимfalse
.
И если задача была бы выполнена тогда
true
.
Эти значения не совсем понятны простым пользователям. Было бы неплохо если бы при обращении к свойствам появлялось понятное сообщение.
Эту проблему можем решить используя свойство Get.
Давайте добавим классу
Task
это свойство.
Для этого в классе после конструктора я напишу слова
get
далее - название done
, пару кругу скобок и пару фигуры скобок. Очень похоже на метод.
Но на самом деле это и есть метод.
Свойство get связывает свойства объекта с функцией, которая будет вызываться при обращении к этому свойству или методу.
В теле метода мы вернём строку в соответствии со значением свойства done.
Если done будет равняться true, то мы вернем одно значение если false то другое.
Давайте посмотрим на результат и мы увидим ошибку.
Которая говорит нам о том что не удалось указать свойство get .
Дело в том что название свойств Get и Set не должны совпадать с названием основных свойств объекта!
В нашем примере объявляя свойство get done() мы перезаписываемый значение done которое изначально указанно в конструкторе.
Есть несколько способов решить эту проблему и все они сводятся к переименование названий.
В других языках, например C#, названия личных свойств, там они называются полями, пишется с маленькой буквы, а публичных свойств с большой.
В JavaScript не принято называть свойства или метода заглавные буквы.
Также JavaScript свойства и методы в классах не делятся на личные и публичные.
Они всегда публичные!
И чтобы решить эту проблему, перед названием свойства методов которые мы хотим скрыть, часто ставят нижнее подчеркивание.
Давайте перед свойством done поставить нижнее подчеркивание.
this._done = false;
Это не означает что мы скрыли свойство или метод. К ним также легко получить доступ, но теперь хотя бы понятно что эти свойства личные.
class Task { constructor( title = Task.getDefaultTitle() ) { this.title = title; this._done = false; Task.count += 1; console.log('Происходит создание задачи'); } get done() { return this._done === true ? 'Выполнено' : 'Не выполнено'; } complete() { this.done = true; console.log(`Задача "${this.title}" выполнена!`); } static getDefaultTitle() { return "Задача"; } }; Task.count = 0; let task = new Task("Выучить JavaScript"); console.log(task.done, task._done);
Давайте попробуем выполнить задачу
task.complete(); console.log(task.done, task._done);
И ещё раз посмотрим.
И мы получили ошибку!
Это произошло потому что мы объявили свойство get но в методе .complete() мы присваиваем this.done значение true, но при этом свойства set мы не указали.
complete() {
this.done = true;
console.log(`Задача "${this.title}" выполнена!`);
}
Давайте добавим классу Task свойство set.
Свойство set
. Для этого после get я напишу set дальше название свойства.Отличие set от get заключается в том что set принимает параметр ,который мы можем присвоить свойству, предварительно проверив этот параметр.
Вы можете назвать его как угодно но чаще всего его называют value.
И в теле метода мы проверим наличие значения value и Является ли это значение типом boolean. Если мы прошли проверку это свойство мы присвоим this._done.
Если же мы проверку не прошли то в консоли мы выдадим ошибку.
class Task {
constructor( title = Task.getDefaultTitle() ) {
this.title = title;
this._done = false;
Task.count += 1;
console.log('Происходит создание задачи');
}
get done() {
return this._done === true ? 'Выполнено' : 'Не выполнено';
}
set done(value) {
if(value!== undefined && typeof value === 'boolean') {
this._done = value;
} else {
console.error("Ошибка! Укажите значение true или false.");
}
}
complete() {
this.done = true;
console.log(`Задача "${this.title}" выполнена!`);
}
static getDefaultTitle() {
return "Задача";
}
};
Task.count = 0;
let task = new Task("Выучить JavaScript");
console.log(task.done, task._done);
task.complete();
console.log(task.done, task._done);
Давайте посмотрим на результат и мы видим что задача успешно выполнена!
Давайте попробуем присвоить, допустим Один.
complete() {
this.done = 1;
console.log(`Задача "${this.title}" выполнена!`);
}
И мы видим сообщение об ошибке.
О том что мы указали неверные значения.
Свойств можем указать сколько угодно.
В нашем примере мы можем переделать свойств title для того чтобы она использовала get и set. нам даже не обязательно иметь внутреннее свойство для того чтобы воспользоваться get и set.
Тем самым мы получим так называемая псевдо-свойства.
У классов, также как у объектов в ES6, название свойств методов может быть вычислено динамически, используя новый синтаксис - квадратные скобки.
У нас в конце-концов получился вполне себе полноценный класс.
И напоследок Давайте взглянем на то что сгенерировал Babel.
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Task = function () { function Task() { var title = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Task.getDefaultTitle(); _classCallCheck(this, Task); this.title = title; this._done = false; Task.count += 1; console.log('Происходит создание задачи'); } _createClass(Task, [{ key: 'complete', value: function complete() { this.done = 1; console.log('\u0417\u0430\u0434\u0430\u0447\u0430 "' + this.title + '" \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0430!'); } }, { key: 'done', get: function get() { return this._done === true ? 'Выполнено' : 'Не выполнено'; }, set: function set(value) { if (value !== undefined && typeof value === 'boolean') { this._done = value; } else { console.error("Ошибка! Укажите значение true или false."); } } }], [{ key: 'getDefaultTitle', value: function getDefaultTitle() { return "Задача"; } }]); return Task; }(); ; Task.count = 0; var task = new Task("Выучить JavaScript"); console.log(task.done, task._done); task.complete(); console.log(task.done, task._done);
Здесь очень много сгенерированного кода но в целом, в принципе, можно различить название класса Task свойства и пр.
Если выписали объектно-ориентированный JavaScript используя ES5, то вы писали, скорее всего, что-то похожее на то что сгенерировал Babel. Не совсем конечно, но близко.
Лично я очень рад что в JavaScript наконец-то появились классы.
Но многие программисты могут со мной не согласится, так как JavaScriptэто больше функциональный язык нежели чем классический.
А прелесть JavaScript заключается в том, что если выходите пользоваться классами, то можете пользоваться классами если хотите писать функциональной код Можете писать функциональный код. Здесь каждый найдёт что-то по душе!
Далее рассмотрим наследование.
Комментариев нет:
Отправить комментарий