Класс — это образец создания объектов. Он определяет свойства и методы, которыми будут обладать объекты, созданные этим классом. Введение классов в ECMAScript (ES6) было направлено на обеспечение более структурированного и объектно-ориентированного способа создания объектов и управления ими.

В этой статье мы рассмотрим следующие темы:

  • Как создать класс.
  • Использование конструкторов в классах.
  • Статические методы и свойства
  • Геттеры и сеттеры
  • Наследование
  • Как использовать TypeScript для улучшения классов

Синтаксический сахар

Раньше мы могли создавать объекты с прототипами, но синтаксис был слишком многословным. В результате были введены классы, и новый класс просто наследует, что такое прототипы и их методы.

До ES6:

function Book(title, author, releaseYear){
  this.title = title;
  this.author = author;
  this.releaseYear = releaseYear;
}

Book.prototype.getOverview = function(){
  return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
}

const book = new Book("Atomic Habits", "James Clear", 2018);

console.log(book.getOverview());

После ES6:

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

  getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}

const book = new Book("Atomic Habits", "James Clear", 2018);

console.log(book.getOverview());

Определение класса

Чтобы определить класс, нам нужно использовать ключевое слово class, за которым следует его имя и фигурные скобки.

class Book {}

Класс может иметь constructor — специальный метод внутри класса, который автоматически вызывается при создании экземпляра класса или объекта. Целью constructor является инициализация состояния объекта или выполнение любых операций настройки, необходимых для правильной работы объекта. В нашем примере мы присваиваем нашему классу свойства, к которым мы сможем получить доступ позже.

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

Примечание. Классы могут иметь только один constructor. JavaScript не поддерживает перегрузку конструкторов или методов.

Классы могут содержать свойства, примитивные или непримитивные типы и методы (функции). Хотя можно явно добавлять свойства к классу за пределами constructor, рекомендуемый подход — присваивать их непосредственно внутри constructor.

class Book {
  // This is possible, but redundant in this scenario since we've already defined these properties within the constructor.
  title;
  author;
  releaseYear;
  constructor(title, author, releaseYear){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }
}

Для определения методов в классах мы используем синтаксис, аналогичный функциям, но без необходимости использования ключевого слова function.

class Book {
  constructor(title, author, releaseYear){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }
  
  // We omit the function keyword
  getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}

Чтобы сделать класс полезным, нам нужно создать экземпляр объекта этого класса, используя ключевое слово new, за которым следует вызов constructor с правильными аргументами:

const book = new Book("Atomic Habits", "James Clear", 2018);

Если мы не предоставим constructor, внутри будет установлено значение по умолчанию:

constructor(){}

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

Статическое ключевое слово

Доступ к методу или свойству static можно получить непосредственно из самого класса, без необходимости создания экземпляра класса. Эта функция особенно ценна для служебных функций или постоянных переменных.

class Utilities {
  static PI = 3.14;

  static max(a, b){
    return a > b ? a : b;
  }
}

console.log(Utilities.PI); // Output: 3.14
console.log(Utilities.max(1,2)); // Output: 2

Если вы знакомы с классом JavaScript Math, понимание этой концепции станет более простым.

Частная недвижимость

К частному свойству или методу невозможно получить доступ извне класса. Чтобы определить такое свойство или метод, мы используем символ #.

class Book {
  #ISBN = 5;
  constructor(title, author, releaseYear, price){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
    this._price = price;
  }

  #getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}

const book = new Book("Atomic Habits", "James Clear", 2018, 120);

//This will throw an error: "getOverview is not a function"
book.getOverview();

//This will return undefined
book.ISBN;

Геттеры и сеттеры

Свойства предлагают гибкий способ получения и установки значений без необходимости прямого доступа к базовым данным. Эта гибкость повышает удобство сопровождения и инкапсуляцию кода.

Мы можем улучшить наш класс Book, введя частное свойство с именем price. Чтобы защитить его от недопустимых значений, мы будем использовать метод установки, сохраняя при этом возможность доступа к нему с помощью метода получения.

class Book {
  constructor(title, author, releaseYear, price){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
    this._price = price;
  }
  
  // Getter
  get price() {
    return this._price;
  }

  // Setter
  set price(newPrice){
    if(this._price === 0) throw new Error("Price can't be zero");
    this._price = newPrice;
  }
}

// Usage

const book = new Book("Atomic Habits", "James Clear", 2018, 120);

console.log(`Price: $${book.price}`);

book.price = 10;

console.log(`Price: $${book.price}`);

Стоит отметить, что мы не вызывали метод установки явно; вместо этого мы использовали прямое присвоение. То же самое относится и к методу получения. Эта функция предлагает удобный и элегантный подход к работе со свойствами нашего класса.

Наследование

Для начала важно понять фундаментальные концепции суперкласса и производных классов.

Суперкласс, также известный как базовый класс или родительский класс, — это класс, от которого другие классы наследуют свойства и методы.

Производный класс, также известный как подкласс или дочерний класс, — это класс, который наследует атрибуты и методы суперкласса.

Наследование — это фундаментальная концепция объектно-ориентированного программирования, обеспечивающая повторное использование кода, расширяемость и организацию классов в иерархии.

Например, мы хотим создать Technical Book со свойством издания. Мы знаем, что Technical Book — это Book, поэтому мы расширяем класс Book, чтобы сделать его свойства доступными для нашего нового класса.

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

class TechnicalBook extends Book {
  constructor(title, author, releaseYear, price, edition){
    super(title, author, releaseYear, price);
    this.edition = edition;
  }
}

const technicalBook = new TechnicalBook("Atomic Habits", "James Clear", 2018, 120, 1);

console.log(`Edition ${technicalBook.edition} of the book ${technicalBook.title}`);

Мы используем ключевое слово super для вызова конструктора суперкласса Book из нашего производного класса TechnicalBook. Это делается для доступа и выполнения поведения, определенного в суперклассе, которое затем можно расширить или настроить в производном классе.

Использование машинописного текста

TypeScript — это статически типизированная расширенная версия JavaScript, которая поддерживает классы для объектно-ориентированного программирования.

Модификаторы доступа

Существует 3 модификатора доступа:

  • public — доступ к свойствам и методам возможен вне класса.
  • private — доступ к свойствам и методам возможен только внутри класса. Производные классы не имеют доступа к этим свойствам или методам.
  • protected — доступ к свойствам и методам возможен только внутри класса или производных классов.

По умолчанию все методы и свойства являются частными.

class Book implements IBook {
  public title: string;
  private author: string;
  protected releaseYear: number;

  public constructor(title: string, author: string, releaseYear: number){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }

  public getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}

Взгляните на этот хитрый метод использования модификаторов доступа для непосредственной инициализации свойств в списке параметров конструктора.

class Book {
  public constructor(public title: string, private author: string, protected releaseYear: number){}

  public getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}

Интерфейсы

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

interface IBook {
  title: string;
  author: string;
  releaseYear: number;
  getOverview: () => string;
}

class Book implements IBook {
  public title: string;
  public author: string;
  public releaseYear: number;

  public constructor(title: string, author: string, releaseYear: number){
    this.title = title;
    this.author = author;
    this.releaseYear = releaseYear;
  }

  getOverview(){
    return `The book ${this.title} written by ${this.author} was released in ${this.releaseYear}.`;
  }
}

Абстрактный класс

В TypeScript абстрактный класс — это класс, экземпляр которого не может быть создан сам по себе, но служит основой для других классов. Абстрактные классы позволяют определять общие методы и свойства, которые должны быть реализованы производными классами. Чтобы создать абстрактный класс в TypeScript, вы используете ключевое слово abstract. Вот базовый пример:

abstract class Shape {
  abstract calculateArea(): number;

  displayArea() {
    console.log(`Area: ${this.calculateArea()}`);
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }

  calculateArea(): number {
    return Math.PI * Math.pow(this.radius, 2);
  }
}

const circle = new Circle(5);

circle.displayArea(); // Output: Area: 78.53981633974483

Если эта статья показалась вам интересной, пожалуйста, подумайте о том, чтобы оставить комментарий или оставить отзыв. Ваши отзывы мотивируют меня создавать больше контента. Если у вас есть какие-либо вопросы или конкретные темы, о которых вы хотели бы, чтобы я написал, пожалуйста, оставляйте комментарии со своими предложениями. Я ценю вашу поддержку и вклад!

Стеккадемический

Спасибо, что дочитали до конца. Прежде чем уйти:

  • Пожалуйста, рассмотрите возможность аплодировать и следовать автору! 👏
  • Подпишитесь на нас в Twitter(X), LinkedIn и YouTube.
  • Посетите Stackademic.com, чтобы узнать больше о том, как мы демократизируем бесплатное образование в области программирования во всем мире.