Translate

Поиск по этому блогу

среда, 19 сентября 2018 г.

ES6: Классы (IX).

Классы в 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 заключается в том, что если выходите пользоваться классами, то можете пользоваться классами если хотите писать функциональной код Можете писать функциональный код. Здесь каждый найдёт что-то по душе!

Далее рассмотрим наследование.



                                                                                                                                                             

Комментариев нет:

Отправить комментарий



Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.Пишите мне - kolesnikovy70 почта gmail.com