В JavaScript не всегда была возможность создавать классические объекты, которые могут быть созданы на таких языках, как C ++ и Java, но теперь вы можете это сделать с момента разработки EcmaScript 6. В этой статье я расскажу, как создавать классы в JavaScript, включая то, как реализовать конструкторы и методы доступа (геттеры и сеттеры), я расскажу о других аспектах классов JavaScript в будущих статьях.

Классы до EcmaScript 6

В предыдущих версиях JavaScript (EcmaScript) вы не могли создать класс так, как это возможно в таких языках, как C ++ и Java. Вместо этого вам нужно было создать функцию-конструктор, а затем назначить методы прототипу конструктора. Следующий пример демонстрирует эту технику:

function Student(name, id, grades) {
  this.name = name;
  this.id = id;
  this.grades = grades;
}
Student.prototype.calcAverage = function() {
  let sum = 0;
  for (grade of this.grades) {
    sum += grade;
  }
  return sum / this.grades.length;
}
let stu1 = new Student("Jane Smith", 1234, [81, 77, 92]);
print("Grade average: " + stu1.calcAverage());
// displays 83.33333333333

Классические языки объектно-ориентированного программирования (ООП) не так определяют объекты и методы классов, но это был ближайший способ сделать это в предыдущих версиях JavaScript.

Создание классов в JavaScript сейчас

Текущая версия JavaScript теперь позволяет создавать классы, которые более тесно связаны с тем, как их создают классические языки ООП. Давайте начнем с того, что мы можем определить приведенный выше пример студента как класс:

class Student {
  constructor(name, id, grades) {
    this.name = name;
    this.id = id;
    this.grades = grades;
  }
  
  calcAverage() {
    let sum = 0;
    for (let grade of this.grades) {
      sum += grade;
    }
    return sum / this.grades.length;
  }
}
let stu1 = new Student("Jane Smith", 1234, [81, 77, 92]);
print("Grade average: " + stu1.calcAverage());

Результат этой программы:

Grade average: 83.33333333333

Определение класса больше похоже на то, как это делается в других языках ООП. Ключевое слово class указывает на то, что определение класса приближается. Конструктор создается не с именем класса, а с ключевым словом constructor. Тем не менее, как вы можете видеть в примере программы, функция-конструктор по-прежнему вызывается с использованием имени класса.

Определения классов - это синтаксический сахар

Определение класса - это просто синтаксический сахар, который скрывает стандартный способ создания прототипов объектов в JavaScript. Это означает, что я мог бы определить класс Student выше, используя более старый синтаксис объекта JavaScript, например этот (этот пример изменен из примера в книге Николаса Закаса Понимание EcmaScript 6 на стр. 168):

let Student = function(name, id, grades) {
  "use strict";
  const Student = function(name, id, grades) {
    if (typeof new.target === "undefined") {
      throw new Error("Constructor must be called with new.");
    }
    this.name = name;
    this.id = id;
    this.grades = grades;
  }
  Object.defineProperty(Student.prototype, "calcAverage", {
    value: function() {
      if (typeof new target !== "undefined") {
        throw new Error("Method cannot be called with new.");
      }
      let sum = 0;
      for (let grade of this.grades) {
        sum += grade;
      }
      return sum / this.grades.length;
  },
  enumerable: false,
  writable: true,
  configurable: true
  });
  return Student;
}());

Понятно, что синтаксис класса значительно упрощает создание классов, чем старый способ.

Однако здесь есть несколько вещей, на которые следует обратить внимание. Во-первых, весь код внутри определения класса по умолчанию выполняется в строгом режиме. Кроме того, все методы по умолчанию определены как неперечислимые. Методы класса нельзя вызывать с ключевым словом new, но конструкторы классов должны вызываться с ключевым словом new.

Как правило, этот новый синтаксис JavaScript для создания классов предоставляет вам множество функций без необходимости писать более сложный объектно-ориентированный синтаксис, необходимый в предыдущих версиях JavaScript.

Конструкторы классов

Как показано в приведенных выше примерах, классы необходимо создавать с помощью конструктора. Вот еще одно определение класса с определенным конструктором:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  show() {
    return this.name + ", " + this.age;
  }
}

Если вы не определяете конструктор, система автоматически предоставит вам конструктор по умолчанию. Вот пример, демонстрирующий это:

class Person {
  show() {
    return this.name + ", " + this.age;
  }
}
let you = new Person();
print(you.show());

Результат этой программы:

undefined, undefined

Несмотря на то, что я не определил метод конструктора, программа не вылетела из-за того, что был создан конструктор по умолчанию и назначен undefined для name и age.

Вы не можете определить конструктор по умолчанию, если у вас уже определен другой конструктор. Классам разрешено только одно определение конструктора или, другими словами, им разрешено одно использование ключевого слова constructor. Система предоставляет конструктор по умолчанию, если конструкторы не определены или уже определен другой конструктор.

Свойства аксессуара

Полное определение класса предоставляет методы доступа (или свойства в случае JavaScript) для получения и установки значений свойств. Их часто называют геттерами и сеттерами на языках ООП.

Начнем с приобретения действующей собственности. Ключевое слово для использования в определении - get. Это свойство просто возвращает значение, хранящееся в свойстве. Вот определение класса Person со свойством получения:

class Person {
  constructor(name, age) {
    this.name = name;
    this.pAge = age;
  }
  get age() {
    return this.pAge;
  }
  show() {
    return this.name + ", " + this.age;
  }
}
let me = new Person("John Green", 24);
print("Age: " + me.age); // displays Age: 24

Здесь я изменил имя свойства, в котором хранится возраст человека, на pAge, чтобы можно было использовать age в качестве свойства-получателя.

Теперь давайте добавим сеттер в определение класса. Наличие установщика позволяет выполнять некоторую проверку данных при установке значения свойства. Например, для свойства age может быть задано любое допустимое число, поэтому возраст может составлять -2000 или 123 456 лет. Давайте определим сеттер для age, чтобы он принимал только значения от 0 до 120.

Вот определение нового класса с этим сеттером:

class Person {
  constructor(name, age) {
    this.name = name;
    if (age >= 0 && age <= 120) {
      this.pAge = age;
    }
    else {
      this.pAge = 0;
    }
  }
  get age() {
    return this.pAge;
  }
  set age(value) {
    if (value >= 0 && value <= 120) {
      this.pAge = value;
    }
    else {
      this.pAge = 0;
    }
  }
  show() {
    return this.name + ", " + this.age;
  }
}
let me = new Person("John Green", 24);
print("Age: " + me.age); // displays Age: 24
me.age = 121;
print("Age: " + me.age); // displays 0
me.age = 25;
print("Age: " + me.age); // displays Age: 25

Просматривая определение моего класса, мне также нужно добавить этот код проверки данных в мой конструктор, например:

constructor(name, age) {
  this.name = name;
  if (age >= 0 && age <= 120) {
    this.pAge = age;
  }
  else {
    this.pAge = 0;
  }
}

Другое решение - написать функцию для проверки возраста и вызова функции из конструктора и установщика. Я оставлю это читателю в качестве упражнения.

Далее в части 2

Я лишь прикоснулся к созданию классов в JavaScript. В следующей статье я расскажу о других возможностях классов, в том числе о том, как рассматривать классы как объекты первого класса, вычислять имена членов и статические члены класса. После этой статьи я перейду к наследованию классов в JavaScript.

Благодарим за прочтение. Присылайте комментарии и предложения по адресу [email protected]. Если вас интересуют мои онлайн-курсы по программированию, посетите https://learningcpp.teachable.com.