Когда вы начинаете писать простые JavaScript программы, вам не нужно волноваться о количестве используемых вами переменных или о том, как работают разные функции и объекты вместе.
Например, большинство людей начинают с использования большого количества глобальных переменных или переменных, охватывающих самый высокий уровень файла. Они не являются частью ни одного класса, объекта или функции.
Например, это глобальная переменная с называнием “состояние”:
let state = "global";
Но как только ваша программа задействует множество различных функций и/или объектов вы будете вынуждены ограничить свой код более строгими правилами.
И тогда в игру вступает понятие “состояние”. Оно описывает текущее состояние всей программы или отдельного объекта: текста, числа, булевого типа данных и др.
Это общий инструмент для приведения кода в порядок. Например, вы один раз обновили состояние и группа различных функций мгновенно изменилась, отреагировав на это обновление.
Но знаете что? Даже “состояние” может вызвать у вас головную боль, как только ваш код становится более сложным! Изменение состояния может привести к непредусмотренным последствиям.
Давайте остановимся здесь. “Состояние” — это популярный инструмент в объектно-ориентированном программировании (ООП).
Но многие программисты предпочитают функциональное программирование, которое не предусматривает возможность изменения состояния. JavaScript поддерживает оба подхода.
Я думаю, достаточно говорить о терминологии. Я хотел показать, как с помощью ООП и функционального программирования можно достичь одинаковых результатов, учитывая, что в последнем не используется состояние.
В этом руководстве показано, как можно приготовить блюдо из спагетти с соусом с объектно-ориентированной и функциональной точек зрения.
Вот быстрый предпросмотр этих двух подходов:
Давайте разберемся с этим. Для того чтобы понять данное руководство, вам нужно разобраться с понятиями “функция” и “объект” в JavaScript.
Объектно-ориентированный метод (с состояниями)
На картинке выше были показаны два различных подхода к приготовлению ужина из макарон:
- Метод, сфокусированный на состояниях различных инструментов, таких как плита, кастрюля и макароны.
- Метод, сфокусированный на приготовлении еды самой по себе без упоминания состояний отдельных инструментов (кастрюли, плиты и др.)
Объектно-ориентированный подход сфокусирован на обновлении состояния, поэтому код будет иметь состояния двух различных уровней:
- Глобального (состояния всего блюда).
- Локального (для каждого объекта).
В этом руководстве мы будем использовать 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
Ух! Этого достаточно для одного раза. Подытожим наши действия:
- Мы создали новый прототип для “макароны”, в котором каждый объект будет иметь локальное состояние, и назвали его
cookedStatus
. - Мы сделали новый экземпляр макарон и назвали его
myMeal
. - Мы использовали состояние из
pastaPot
, которое мы создали в последнем фрагменте кода, чтобы определить, должны ли мы обновить состояниеcookedStatus
вmyMeal
до “приготовлено”. - Когда состояние статуса приготовления (
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, в функцию лучше передавать только один аргумент?