Перейти к содержанию

Классы в современном JavaScript


Рекомендуемые сообщения

  • Админ

Классы
В современном JavaScript появился новый, «более красивый» синтаксис для классов.
Новая конструкция class – удобный «синтаксический сахар» для задания конструктора вместе с прототипом.
Class
Синтаксис для классов выглядит так:
class Название [extends Родитель]  {
  constructor
  методы
}
Например:
'use strict';

class User {

  constructor(name) {
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }

}

let user = new User("Вася");
user.sayHi(); // Вася
Функция constructor запускается при создании new User, остальные методы записываются в User.prototype.
Это объявление примерно аналогично такому:
function User(name) {
  this.name = name;
}

User.prototype.sayHi = function() {
  alert(this.name);
};
В обоих случаях new User будет создавать объекты. Метод sayHi также в обоих случаях находится в прототипе.
Но при объявлении через class есть и ряд отличий:
User нельзя вызывать без new, будет ошибка.
Объявление класса с точки зрения области видимости ведёт себя как let. В частности, оно видно только в текущем блоке и только в коде, который находится ниже объявления (Function Declaration видно и до объявления).
Методы, объявленные внутри class, также имеют ряд особенностей:
Метод sayHi является именно методом, то есть имеет доступ к super.
Все методы класса работают в строгом режиме use strict, даже если он не указан.
Все методы класса не перечислимы. То есть в цикле for..in по объекту их не будет.
Class Expression
Также, как и Function Expression, классы можно задавать «инлайн», в любом выражении и внутри вызова функции.
Это называется Class Expression:
'use strict';

let User = class {
  sayHi() { alert('Привет!'); }
};

new User().sayHi();
В примере выше у класса нет имени, что один-в-один соответствует синтаксису функций. Но имя можно дать. Тогда оно, как и в Named Function Expression, будет доступно только внутри класса:
'use strict';

let SiteGuest = class User {
  sayHi() { alert('Привет!'); }
};

new SiteGuest().sayHi(); // Привет
new User(); // ошибка
В примере выше имя User будет доступно только внутри класса и может быть использовано, например, для создания новых объектов данного типа.
Наиболее очевидная область применения этой возможности – создание вспомогательного класса прямо при вызове функции.
Например, функция createModel в примере ниже создаёт объект по классу и данным, добавляет ему _id и пишет в «реестр» allModels:
'use strict';

let allModels = {};

function createModel(Model, ...args) {
  let model = new Model(...args);

  model._id = Math.random().toString(36).slice(2);
  allModels[model._id] = model;

  return model;
}

let user = createModel(class User {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    alert(this.name);
  }
}, "Вася");

user.sayHi(); // Вася

alert( allModels[user._id].name ); // Вася
Геттеры, сеттеры и вычисляемые свойства
В классах, как и в обычных объектах, можно объявлять геттеры и сеттеры через get/set, а также использовать […] для свойств с вычисляемыми именами:
'use strict';

class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  // геттер
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  // сеттер
  set fullName(newValue) {
    [this.firstName, this.lastName] = newValue.split(' ');
  }

  // вычисляемое название метода
  ["test".toUpperCase()]() {
    alert("PASSED!");
  }

};

let user = new User("Вася", "Пупков");
alert( user.fullName ); // Вася Пупков
user.fullName = "Иван Петров";
alert( user.fullName ); // Иван Петров
user.TEST(); // PASSED!
При чтении fullName будет вызван метод get fullName(), при присвоении – метод set fullName с новым значением.
class не позволяет задавать свойства-значения
В синтаксисе классов, как мы видели выше, можно создавать методы. Они будут записаны в прототип, как например User.prototype.sayHi. Однако, нет возможности задать в прототипе обычное значение (не функцию), такое как User.prototype.key = "value". Конечно, никто не мешает после объявления класса в прототип дописать подобные свойства, однако предполагается, что в прототипе должны быть только методы. Если свойство-значение, всё же, необходимо, то можно создать геттер, который будет нужное значение возвращать.
Статические свойства
Класс, как и функция, является объектом. Статические свойства класса User – это свойства непосредственно User, то есть доступные из него «через точку».
Для их объявления используется ключевое слово static.
Например:
'use strict';

class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  static createGuest() {
    return new User("Гость", "Сайта");
  }
};

let user = User.createGuest();

alert( user.firstName ); // Гость

alert( User.createGuest ); // createGuest ... (функция)
Как правило, они используются для операций, не требующих наличия объекта, например – для фабричных, как в примере выше, то есть как альтернативные варианты конструктора. Или же, можно добавить метод User.compare, который будет сравнивать двух пользователей для целей сортировки.
Также статическими удобно делать константы:
'use strict';

class Menu {
  static get elemClass() {
    return "menu"
  }
}

alert( Menu.elemClass ); // menu
Наследование
Синтаксис:
class Child extends Parent {
  ...
}
Посмотрим как это выглядит на практике. В примере ниже объявлено два класса: Animal и наследующий от него Rabbit:
'use strict';

class Animal {
  constructor(name) {
    this.name = name;
  }

  walk() {
    alert("I walk: " + this.name);
  }
}

class Rabbit extends Animal {
  walk() {
    super.walk();
    alert("...and jump!");
  }
}

new Rabbit("Вася").walk();
// I walk: Вася
// and jump!
Как видим, в new Rabbit доступны как свои методы, так и (через super) методы родителя.
Это потому, что при наследовании через extends формируется стандартная цепочка прототипов: методы Rabbit находятся в Rabbit.prototype, методы Animal – в Animal.prototype, и они связаны через __proto__:
'use strict';

class Animal { }
class Rabbit extends Animal { }

alert( Rabbit.prototype.__proto__ == Animal.prototype ); // true
Как видно из примера выше, методы родителя (walk) можно переопределить в наследнике. При этом для обращения к родительскому методу используют super.walk().
С конструктором – немного особая история.
Конструктор constructor родителя наследуется автоматически. То есть, если в потомке не указан свой constructor, то используется родительский. В примере выше Rabbit, таким образом, использует constructor от Animal.
Если же у потомка свой constructor, то, чтобы в нём вызвать конструктор родителя – используется синтаксис super() с аргументами для родителя.
Например, вызовем конструктор Animal в Rabbit:
'use strict';

class Animal {
  constructor(name) {
    this.name = name;
  }

  walk() {
    alert("I walk: " + this.name);
  }
}

class Rabbit extends Animal {
  constructor() {
    // вызвать конструктор Animal с аргументом "Кроль"
    super("Кроль"); // то же, что и Animal.call(this, "Кроль")
  }
}

new Rabbit().walk(); // I walk: Кроль
Для такого вызова есть небольшие ограничения:
Вызвать конструктор родителя можно только изнутри конструктора потомка. В частности, super() нельзя вызвать из произвольного метода.
В конструкторе потомка мы обязаны вызвать super() до обращения к this. До вызова super не существует this, так как по спецификации в этом случае именно super инициализирует this.
Второе ограничение выглядит несколько странно, поэтому проиллюстрируем его примером:
'use strict';

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Rabbit extends Animal {
  constructor() {
    alert(this); // ошибка, this не определён!
    // обязаны вызвать super() до обращения к this
    super();
    // а вот здесь уже можно использовать this
  }
}

new Rabbit();
Итого
Классы можно объявлять как в основном потоке кода, так и «инлайн», по аналогии с Function Declaration и Expression.
В объявлении классов можно использовать методы, геттеры/сеттеры и вычислимые названия методов.
При наследовании вызов конструктора родителя осуществляется через super(...args), вызов родительских методов – через super.method(...args).
Концепция классов, которая после долгих обсуждений получилась в стандарте ECMAScript, носит название «максимально минимальной». То есть, в неё вошли только те возможности, которые уж точно необходимы.
В частности, не вошли «приватные» и «защищённые» свойства. То есть, все свойства и методы класса технически доступны снаружи. Возможно, они появятся в будущих редакциях стандарта.

Telegram сайта  "Типичный социум"

Do not be indifferent. Support for motivation to continue on to engage in this further! Thanks

img.png

Ссылка на комментарий
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

Обжалование или подача апелляции на снятие Бана в группе "Типичный Социум IT!
Заявки и обжалование отправлять в Телеграм bot @ModeratorTS_Bot


×
×
  • Создать...

Важная информация

Мы разместили cookie-файлы на ваше устройство, чтобы помочь сделать этот сайт лучше. Вы можете изменить свои настройки cookie-файлов, или продолжить без изменения настроек.Политика конфиденциальности

Write what you are looking for and press enter or click the search icon to begin your search

-->