Классы, методы и свойства, Class Expression

На предыдущих занятиях мы с вами работали с объектами, расширяли их по распространенному синтаксису с использованием свойств:

__proto__ и prototype.

Однако, относительно недавно в JavaScript появилась возможность объявлять классы для создания объектов по общепринятому во многих языках программирования синтаксису – через ключевое слово class:

class <название> {
   constructor() { … }
   method1() { … }
   …
   methodN() { … }
}

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

class Book {
         constructor() {
                   console.log("создание объекта book");
         }
}

И, затем, с помощью оператора new создать объект по этому шаблону:

let book1 = new Book();

В результате увидим строчку «создание объекта book», означающая, что автоматически был вызван метод constructor класса Book. Если кто не знает, это специальный метод, который вызывается при создании объекта. В нем можно выполнять начальную инициализацию каких-либо свойств. Например, мы хотим передавать объекту, при его создании, несколько параметров:

заголовок, автор и цена книги

Для этого в конструкторе определим эти параметры, затем, проинициализируем их, то есть, создадим соответствующие свойства в объекте:

class Book {
         constructor(title, author, price) {
                   this.title = title;
                   this.author = author;
                   this.price = price;
         }
}

И при создании объекта, передадим этим параметрам определенные значения:

let book1 = new Book("Муму", "Тургенев", 112);

Выведем в консоль этот объект:

console.log(book1);

и увидим все эти, созданные в конструкторе, свойства. И, разумеется, ссылка this будет указывать на новый созданный объект, а не на класс Book.

Затем, в этот класс можно поместить несколько методов, например, такие:

         getTitle() { return this.title; }
         setPrice(pr) { this.price = pr; }

Обратите внимание, мы здесь не пишем ключевое слово function, а объявляем методы, используя синтаксис объектов, но без запятых в конце.

И, теперь смотрите, при выводе в консоль объекта book1 мы не увидим в нем этим методов. Они будут находиться в базовом объекте, на который ссылается свойство __proto__:

Отличия классов от функций

Здесь может показаться, что, в действительности, класс в JavaScript работает наподобие функции-конструктора:

function Book(title, author, price) {
         this.title = title;
         this.author = author;
         this.price = price;
}
 
Book.prototype.getTitle = function() { return this.title; }
Book.prototype.setPrice = function(pr) { this.price = pr; }

И вы будете близки к истине. Действительно, если вернуться к классу и отобразить его тип:

console.log(typeof(Book));

то увидим значение «function». Но есть и отличия, например, класс создает функцию не в «чистом» виде. Это специальная функция, которая помечена как

[[FunctionKind]]:"classConstructor"

и без оператора new она не может быть вызвана. Например, так:

let book1 = Book("Муму", "Тургенев", 112);

Далее, методы класса автоматически помечаются как неперечисляемые и не выводятся с помощью цикла for in:

for(let p in book1) 
         console.log(p+": "+book1[p]);

Наконец, весь код внутри классов автоматически помечается директивой

"use strict"

Так что отличия от обычной функции все же есть.

Class Expression

Подобно функциям классы можно определять по синтаксису Class Expression в следующем виде:

let Book = class {
         constructor(title, author, price) {
                   this.title = title;
                   this.author = author;
                   this.price = price;
         };
}

Причем, использовать класс можно только после его объявления как бы он ни был объявлен. В отличие от функций, которые можно вызывать в скрипте до их определения, если они заданы по синтаксису Function Declaration.

Также, подобно функциям, классам можно назначать имя при следующем определении:

let Book = class BookClass {
         constructor(title, author, price) {
                   this.title = title;
                   this.author = author;
                   this.price = price;
         };
}

Причем, это второе имя будет видно только внутри этого класса. За его пределами можно обращаться только через переменную Book.

Интересной особенностью является возможность создавать классы динамически с помощью функции, например, так:

function createFruit(name, weight) {
         return class {
                   constructor() {
                            this.name = name;
                            this.weight = weight;
                   };
 
                   showInfo() { console.log(this.name + " " + this.weight) };
         }
}

Здесь описана функция, которая формирует новый класс со значениями параметров name и weight, которые будут записаны в объект при его создании. Далее, мы можем создать класс для определенного фрукта:

let f1 = createFruit("Яблоко", 100);

Причем, здесь f1 – это ссылка на класс, а не на созданный объект, то есть, далее, нужно вызвать оператор new и создать объект:

let apple = new f1();
apple.showInfo();

Если вызвать функцию еще раз с другими параметрами:

let f2 = createFruit("Вишня", 40);

то будет создан еще один класс, никак не связанный с предыдущим и, далее, также можно выполнить следующие конструкции:

let v = new f2();
v.showInfo();

То есть, все классы работают совершенно независимо.

Геттеры и сеттеры классов

Часто в классах прописывают специальные методы:

  • геттеры – для получения свойств объекта;
  • сеттеры – для изменения свойств объекта.

В рамках JavaScript такие методы можно объявлять с помощью специальных ключевых слов:

get и set

следующим образом:

         get titleBook() {return this.title; }
         set titleBook(value) { this.title = value; }

И, далее, использовать, просто обращаясь к ним по имени:

let book1 = new Book("Муму", "Тургенев", 112);
console.log( book1.titleBook );
book1.titleBook = "Мы болеем за Муму";
console.log( book1.titleBook );

Это бывает очень удобно.

Свойства классов

Совсем недавно была добавлена возможность описания свойств непосредственно внутри класса, например, так:

class Book {
         pages = 123;
 
         constructor(title, author, price) {
                   this.title = title;
                   this.author = author;
                   this.price = price;
         };
}

Причем, они автоматически становятся частью самого объекта book1, а не базового класса. Мы в этом можем легко убедиться, если выведем объект в консоль:

console.log(book1);

Вот что из себя представляют классы в базовом представлении.