JavaScript

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

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

Например, это глобальная переменная с называнием “состояние”:

let state = "global";

Но как только ваша программа задействует множество различных функций и/или объектов вы будете вынуждены ограничить свой код более строгими правилами.

И тогда в игру вступает понятие “состояние”. Оно описывает текущее состояние всей программы или отдельного объекта: текста, числа, булевого типа данных и др.

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

Но знаете что? Даже “состояние” может вызвать у вас головную боль, как только ваш код становится более сложным! Изменение состояния может привести к непредусмотренным последствиям.

Давайте остановимся здесь. “Состояние” — это популярный инструмент в объектно-ориентированном программировании (ООП).

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

Я думаю, достаточно говорить о терминологии. Я хотел показать, как с помощью ООП и функционального программирования можно достичь одинаковых результатов, учитывая, что в последнем не используется состояние.

В этом руководстве показано, как можно приготовить блюдо из спагетти с соусом с объектно-ориентированной и функциональной точек зрения.

Вот быстрый предпросмотр этих двух подходов:

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

Объектно-ориентированный метод (с состояниями)

На картинке выше были показаны два различных подхода к приготовлению ужина из макарон:

  1. Метод, сфокусированный на состояниях различных инструментов, таких как плита, кастрюля и макароны.
  2. Метод, сфокусированный на приготовлении еды самой по себе без упоминания состояний отдельных инструментов (кастрюли, плиты и др.)

Объектно-ориентированный подход сфокусирован на обновлении состояния, поэтому код будет иметь состояния двух различных уровней:

  1. Глобального (состояния всего блюда).
  2. Локального (для каждого объекта).

В этом руководстве мы будем использовать ES6 синтаксис для создания объектов.

Вот пример глобального состояния и прототип “кастрюля”.

let stoveTemp = 500;
function Pot(){
  this.boilStatus = '';
  this.startBoiling = function(){
    if( stoveTemp > 400)
      this.boilStatus = "boiling";
  }
}
let pastaPot = new Pot();
pastaPot.startBoiling();
console.log(pastaPot);
// Pot { boilStatus = 'boiling'; }

Примечание: яупростил сообщение console.log, чтобы сфокусироваться на обновлениях состояния.

Вот визуальная репрезентация этой логики.

До

После

Здесь два состояния. Когда с помощью прототипа Potсоздается pastaPot, сначала мы получаем пустое состояние boilStatus. Но потом оно меняется.

Мы запускаем pastaPot.startBoiling(), а состояние boilStatus (локальное) меняется на boiling, так как глобальное состояние (температуры) stoveTemp превысило 400 градусов.

Теперь далее. Мы позволим макаронам свариться благодаря состоянию pastaPot.

Вот код, который мы добавим к указанному выше фрагменту:

function Pasta (){
  this.cookedStatus = false;
  this.addToPot = function (boilStatus){
    if(boilStatus == "boiling")
      this.cookedStatus = true;
  }
}
let myMeal = new Pasta();
myMeal.addToPot(pastaPot.boilStatus);
console.log(myMeal.cookedStatus);
// true

Ух! Этого достаточно для одного раза. Подытожим наши действия:

  1. Мы создали новый прототип для “макароны”, в котором каждый объект будет иметь локальное состояние, и назвали егоcookedStatus.
  2. Мы сделали новый экземпляр макарон и назвали его myMeal.
  3. Мы использовали состояние из pastaPot , которое мы создали в последнем фрагменте кода, чтобы определить, должны ли мы обновить состояние cookedStatus в myMeal до “приготовлено”.
  4. Когда состояние статуса приготовления (boilStatusв pastaPot) стало “кипит”, наши макароны приготовились!

Визуально это выглядит так:

До

После

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

Функциональный метод (без состояний)

Чтобы полностью понять, что такое “состояние”, вам нужно найти способ получить тот же самый итог, как и в коде выше, но без изменения состояния. Вот где пригодится функциональное программирование!

У функционального программирования есть две величины, отличающие его от ООП: неизменяемость и строгие функции.

Оба данных принципа исключают изменение состояния в коде. То есть, мы не можем использовать локальное или глобальное состояния.

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

Вот пример функции, которая сварит макароны:

const stoveTemp = 500;
const cookPasta = (temp) => {
  if(temp > 400)
    return 'cooked';
}
console.log(cookPasta(stoveTemp));
// 'cooked'

Этот код успешно вернет строку “приготовлено”. Но, обратите внимание, здесь нет объекта, который мы обновляем. Функция просто возвращает значение, которое будет использовано на следующем этапе.

Мы сфокусированы на вводе и выводе одной функции: cookPasta.

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

Вот на что похожа эта функция:

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

А теперь разберем вторую часть, в которой подается ужин.

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

const serveMeal = (pasta) => {
 if (pasta == 'cooked')
   return 'Dinner is ready.'
}
console.log( serveMeal(cookPasta(stoveTemp)) );
// 'Dinner is ready.'

Сейчас мы доставляем результаты функции cookPastaнапрямую в функцию serveMeal. И снова мы способны сделать это без изменения состояния или структуры данных.

Вот диаграмма с временной шкалой для демонстрации того, как эти две функции работают вместе:

Перевод статьи Kevin Kononenko: State in JavaScript explained by cooking a simple meal

Читайте так же: Почему в JavaScript, в функцию лучше передавать только один аргумент?