Прототипно-ориентированное программирование в JavaScript

Обложка поста

Рассказывает Илья Махонин, разработчик

Сегодня мы поговорим о работе с классами в JS. Думаю, что все, кто интересуется разработкой, слышали про объектно-ориентированное программирование (ООП). Эта парадигма программирования в настоящее время одна из самых популярных. И сейчас мы узнаем, как она реализуется в языке JS. ООП — очень обширная тема, в ней много понятий, поэтому данная статья не будет исчерпывающей. Мы разберём только основные моменты, не сильно вдаваясь в понятия и термины.

Сперва нужно разобраться с двумя основным идеями ООП — классом и объектом. Класс можно представить как чертёж, то есть проект чего-то, ещё не созданного, а объект — как воплощённый в жизнь чертёж. Если проводить параллель со строительством, то чертёж будущего дома — это класс, а построенный дом — объект.

Сразу хочу отметить, что в JS нет ООП как такового. Вместо него используется прототипно-ориентированное программирование — один из стилей ООП. У него есть отличия, но сейчас я их опущу.

Раньше, в ES5, работа в такой парадигме сводилась к написанию функции конструктора, а затем к неудобным манипуляциям со свойствами этой функции.

В ES6 был добавлен сахарок для работы с прототипами (для удобства буду называть их «классами»). Появились разнообразные команды для удобной разработки, которые скрывают под капотом все те «старые» команды из ES5.

Мы напишем простенький калькулятор, придерживаясь прототипно-ориентированного программирования. Такой пример познакомит вас с данной парадигмой, но не раскроет всех её преимуществ. Это я к тому, чтобы те, кто только начинает работать с JS, не сочли прототипное программирование чем-то непонятным и усложняющим разработку.

Создадим «класс» и объявим в нём конструктор (constructor). В конструкторе создадим две переменные: numberA и numberB. This — это указатель на текущий «класс». Если говорить проще, то теперь объявленные переменные будут доступны везде внутри «класса».

В итоге получится следующее:

class Calculate{
    constructor(numberA, numberB) {
        this.numberA = numberA;
        this.numberB = numberB;
    }
}

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

Наш маленький калькулятор будет поддерживать 4 основные операции. Поэтому создадим метод add(), который будет суммировать переданные ему аргументы. А если таковых нет, то калькулятор просуммирует те самые значения по «умолчанию».

Я не буду добавлять различные проверки вроде «числа ли переданы?», «не на ноль ли делим?» и т.д. Остановлюсь только на том, что должны быть переданы два аргумента, либо будут использоваться значения по умолчанию.

Вот так будет выглядеть метод сложения:

class Calculate{
    constructor(numberA, numberB) {
        this.numberA = numberA;
        this.numberB = numberB;
    }
    
    add(num1 = null, num2 = null) {
        if (num1 === null || num2 === null) {
            return this.numberA + this.numberB;
        }
        return num1 + num2;
    }
}

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

После создания всех методов «класс» приобретёт следующий вид:

class Calculate{
    constructor(numberA, numberB) {
        this.numberA = numberA;
        this.numberB = numberB;
    }
    
    add(num1 = null, num2 = null) {
        if (num1 === null || num2 === null) {
            return this.numberA + this.numberB;
        }
        return num1 + num2;
    }
    sub(num1 = null, num2 = null) {
        if (num1 === null || num2 === null) {
            return this.numberA - this.numberB;
        }
        return num1 - num2;
    }
    mul(num1 = null, num2 = null) {
        if (num1 === null || num2 === null) {
            return this.numberA * this.numberB;
        }
        return num1 * num2;
    }
    div(num1 = null, num2 = null) {
        if (num1 === null || num2 === null) {
            return this.numberA / this.numberB;
        }
        return num1 / num2;
    }
}

Основная часть работы сделана. Осталось «создать экземпляр класса» и протестировать методы на работоспособность. Объявим константу calculate и присвоим ей новый объект. Посмотрите на пример, чтобы всё стало ясно:

const calculate = new Calculate(72, 39);
console.log(calculate.add());
console.log(calculate.sub(1000, 250));
console.log(calculate.mul(926));
console.log(calculate.div(25, 5));

Команда new используется для создания нового «экземпляра класса». Значения по умолчанию в этом примере — 72 и 39. Их мы передаём в «класс» как аргументы в обычную функцию. По сути, «класс» это и есть простая функция с некоторыми отличиями.

После описанных выше действий вызовем все четыре метода, передав в некоторые из них аргументы. Проверим, корректно ли они работают:

111
750
2808
5

Да, всё правильно. Только в случае передачи двух аргументов, метод использует их. В остальных случаях используются значения по умолчанию.

Сегодня я познакомил вас с прототипно-ориентированным программированием в JavaScript. Да-да, несмотря на то что ключевое слово для создания прототипа — class, это всё те же прототипы. Именно поэтому я брал слова «класс» и «экземпляр» в кавычки.

Не смешно? А здесь смешно: @ithumor