Акт 1: «Ядро» — Часть 14 (Занятия)
Это продолжение серии о JavaScript, начатой здесь.
Классы — это жемчужины принципов объектно-ориентированного программирования. В общем, они обеспечивают хороший способ структурировать код и избежать дублирования кода. Тем не менее, следует отметить, что использование классов и ООП-принципов в целом затмевается тем, что приводит к неподдерживаемому коду, особенно при чрезмерном использовании полиморфизма (наследования).
Классы:
Основная цель классов — улучшить структурирование кода. Код внутри классов автоматически запускается в строгом режиме (см. здесь) и, следовательно, обеспечивает выполнение в безопасном и производительном контексте.
Основные сведения о классах перечислены в этом примере, который объявляет класс с именем MyClass
и объявляет несколько полей и методов:
class MyClass { #prop1 = 'hello'; // private field prop2 = 7; // public field #prop4 = 'private'; static prop5 = 'I am a static field'; // static field // static and private field static #prop6 = 'I am static and private'; // dynamic key values (here a symbol) [Symbol.iterator] = function () { }; constructor(prop3) { this.prop3 = prop3; // setting non-declared field } get prop1() { // getter for private field return this.#prop1; } set prop4(v) { // setter for private field this.#prop4 = v; console.log(`#prop4 change to '${this.#prop4}'`); } static get prop6() { // getter for a static private field return MyClass.#prop6; } publicMethod() { // public method console.log('public method called'); } #privateMethod() { // private method } *genFunc() { } // generator functions (iterators) static staticMethod() { // static method console.log('static method called'); } }
Поля и методы могут быть закрытыми, статическими или общедоступными.
Частные поля и методы имеют префикс ‘#’. Они недоступны извне класса. В приведенном выше примере некоторые частные поля частично доступны с помощью get
соотв. set
методы.
Экземпляры класса создаются с помощью оператора new
. Конструктор позволяет передавать значения во время создания экземпляра:
const m = new MyClass(true); console.log(m.prop1); // hello m.prop4 = 'setting private field'; // #prop4 change to 'setting private field' m.publicMethod(); // public method called MyClass.staticMethod(); // static method called console.log(MyClass.prop5); // I am a static field console.log(MyClass.prop6); // I am static and private
Публичные поля доступны так же, как и обычные объекты: m.prop1
Доступ к статическим методам и полям возможен только через объект функции, то есть имя класса. Важно понимать, что на статические методы и поля ссылаются не из экземпляра данного класса, а непосредственно из самого объекта класса: MyClass.staticMethod()
, MyClass.prop5
Все методы, объявленные с использованием сокращенной нотации, являются методами экземпляра, то есть они вызываются через экземпляр: m.publicMethod()
Помните, это означает, что при таком вызове его this
-контекст устанавливается как экземпляр класса (здесь m
).
В конце концов, экземпляр класса — это не что иное, как объект. Тем не менее, классы предоставляют некоторые сокращенные обозначения для таких вещей, как создание get, set
методов.
Сами классы — не что иное, как синтаксический сахар. Все, что вы можете реализовать с помощью классов, вы можете сделать и с помощью новых функций (см. здесь). Тем не менее, классы часто предоставляют гораздо более читаемый код. В этом смысле:
get/set
соответствуют дескрипторам свойств.- статические методы и поля объекта, размещенные в объекте-функции.
- частные поля и методы, к переменным и функциям, которые объявлены внутри функции (
var, const, let
,function
).
Наследование:
Классы позволяют наследовать методы и поля от других классов. Это ограниченная версия наследования прототипов:
class A { prop1 = 'prop1 set in A'; #prop2 = 'prop2'; prop3 = 'prop3 from A'; static prop4 = 'static prop4 from A'; constructor(prop1) { this.prop1 = prop1; } method1() { console.log('method1 from A'); } #method2() { console.log('method2 from A'); } method3() { console.log('method3 from B'); } } class B extends A { prop3 = 'prop3 from B'; static prop4 = 'static prop4 from B'; constructor() { super('prop1 set through constructor of B') } method3() { console.log('method3 from B'); } } const b = new B(); console.log(b.prop1); // prop1 set through constructor of B b.method1(); // method1 from A b.method3(); // method3 from B console.log(b.prop3); // prop3 from B console.log(B.prop4); // static prop4 from B
Наследование достигается с помощью ключевого слова extend
. Конструктор дочернего класса должен вызвать метод super
, который ссылается на конструктор родительского класса.
Частные поля и методы не наследуются. Статические методы, хотя и не связанные с экземпляром, наследуются. Не все ОО-языки разделяют это поведение.
Этот конкретный шаблон может применяться глубоко вложенным, то есть вы можете наследовать разные классы от многих классов в иерархии: A1 extends A2 extends A3 ...
Хотя это кажется очень полезным во многих сценариях, это приводит к чрезвычайно сложному сопровождению кода. Вы легко заблудитесь в больших и сложных иерархиях наследования. Поэтому во время кодирования вы всегда должны иметь в виду известное следующее утверждение:
Предпочитайте композицию наследованию!
Мы не пытаемся рассматривать это здесь, но краткое объяснение «композиции» состоит в том, чтобы иметь небольшие классы (или модули), которые предоставляют выделенную функциональность, которую вы можете внедрить, когда это необходимо.
Оператор instanceof
:
Оператор instanceof
, применяемый к экземплярам класса, можно использовать для проверки принадлежности экземпляра к определенному классу:
if(b instanceof B){ console.log('b is a B'); // b is a B } if(b instanceof A){ console.log('b is a A'); // b is a A (since inherited) }
Миксины:
Распространенным шаблоном в объектно-ориентированном программировании является так называемый миксин. Он намеревается создать новый класс, используя функциональность данного класса, дополненную некоторыми предопределенными функциями. Поскольку JavaScript позволяет создавать встроенные анонимные классы, этот шаблон можно реализовать следующим образом:
function mixin(B) { return class extends B { method() { console.log(‘decorating method called’); } } } const A = mixin(class { }); new A().method(); // decorating method called
Метод mixin
принимает в качестве параметра функцию-конструктор B
и возвращает новую функцию-конструктор, которая расширяет B
некоторым дополнительным методом. Особенностью этого шаблона является то, что B
известен только во время выполнения.
На этом пока все, и не забывайте оставлять комментарии по вопросам или ошибкам, которые вы обнаружите. Давайте посмотрим еще раз в следующей истории, если хотите.
Спасибо за чтение!