понедельник, 29 января 2018 г.

Введение в функциональный JavaScript

Вы очевидно слышали что JavaScript является функциональным языком, или по крайней мере поддерживается приемы используемые в в функциональном программировании. Но что такое функциональное программирование? И если уже пойти дальше, если вы хотите разобратся в программных парадигмах в целом, как функциональный подход отличается от того, что вы используете при написании кода на JavaScript в ваших приложениях?
Итак, хорошая новость заключается в том, что JavaScript предоставляет вам право выбора, когда речь заходит о парадигмах. Вы можете смешивать ваш код, написанный в разных стилях: в объектно-ориентированном, в императивном, протипированном и функциональном. Но плохая новость заключается в том, что это значит для вашего кода. JavaScript может поддерживать одновременно различные стили кода для одно и того же приложения, поэтому выбор в принятии решения о том, каким стилем пользоваться – за вами.

Функциональный JavaScript не требует от вас переделывать код для всего проекта. Изучите принципы функционального подхода и это позволит вам принимать правильные решения при создании ваших проектов, вне зависимости от того какой стиль вы используете. Изуччение паттернов и приемов функционального программирования поможет вам писать более понятный и чистый код на JavaScript.
Императивый JavaScript
Первую популярность JavaScript обрел будучи браузерным языком, и использовался в основном для добавления простейшей логики подсветки элементов, обработки кликов на странице. Прошли годы, и это негативно повлияло на репутацию языка.
Поскольку разработчики изо всех сил пытались сопоставить гибкость JavaScript с запутанностью объектной модели документа браузера (DOM), фактический код JavaScript часто выглядел примерно так в реальном мире:
var result;
function getText() {
  var someText = prompt("Give me something to capitalize");
  capWords(someText);
  alert(result.join(" "));
};
function capWords(input) {
  var counter;
  var inputArray = input.split(" ");
  var transformed = "";
  result = [];
  for (counter = 0; counter < inputArray.length; counter++) {
    transformed = [
      inputArray[counter].charAt(0).toUpperCase(),
      inputArray[counter].substring(1)
    ].join("");
    result.push(transformed);
  }
};
document.getElementById("main_button").onclick = getText;
Таким образом, в даном примере происходит много разных событий. Переменные объявляются в глобальном пространстве. Значения передаются и модифицируются в функциях. Методы DOM перемешаны с кодом JavaScript. Названия методов не всегда понятны, и все это осложнено тем, что вся логика полагается на некий контекст, в котором этот код выполнятеся, но которого может и не быть. Но если вы запустите этот код на странице, где есть разметка вида <button id="main_button">, то вы можете увидеть текст и в всплывающий диалог, в котором каждая буква текста стала заглавной.
Имеративный код, написанный здесь, должен читаться и выполнятся сверху вниз (получая или отдавая место для хранения переменных). Но ниже мы рассмотрим несколько улучшений которые позволят сделать код чище и более читаемым. Для этого обратимся к объектно-ориентированному подходу.
Объектно-ориентированный JavaScript
Спустя несколько лет разработчики начали обращать внимание на проблемы с императивным подходом в совмещенном окружении, например в браузере. Глобальные переменные из одного блока кода перекрывались переменными из другого. Порядок в котором вызывался код, влиял на то, как работал код, что иногда приводило к непредсказуемым последствиям, особенно ввиду задежек отклика сети и веремени ренедринга.
Поэтому появился новый подход, который был призван инкапсулировать код JavaScript и осуществить лучшее взаимодействие с DOM. Обновленный вариант подобного кода, написан в объектно-ориентированном стиле:
(function() {
  "use strict";
  var SomeText = function(text) {
    this.text = text;
  };
  SomeText.prototype.capify = function(str) {
    var firstLetter = str.charAt(0);
    var remainder = str.substring(1);
    return [firstLetter.toUpperCase(), remainder].join("");
  };
  SomeText.prototype.capifyWords = function() {
    var result = [];
    var textArray = this.text.split(" ");
    for (var counter = 0; counter < textArray.length; counter++) {
      result.push(this.capify(textArray[counter]));
    }
    return result.join(" ");
  };

  document.getElementById("main_button").addEventListener("click", function(e) {
    var something = prompt("Give me something to capitalize");
    var newText = new SomeText(something);
    alert(newText.capifyWords());
  });
}());
В данной объектно-ориентированной версии кода, функция конструктор симулиует класс такой структуры, которой мы хотим. Методы живут внутри прототипа нового объекта и экономно расходуют память. Весь код изолирован внутри анонимной функции, поэтому он не засоряет глобальное пространство. Здесь также есть вызов директивы "use strict", которая использует последний движок JavaScript а вместо старого метода onclick испольуется обновленный addEventListener, потому что уже мало кто использует IE8 или старше. Подобный скрипт можно вставить в конец элемента &lt;body&gt; на странице, таким образом мы будем уверены, что весь DOM будет загружен прежде чем вызовется наша функция.
Но несмотря на все переформативание кода, здесь все еще присутствует множество артифактов из императивного стиля. Методы в функции конструкторе полагаются на переменные, которые ограничены родительским объектом. Здесь есть циклическая конструкция для итерации по всем элементам массива строк. Есть также переменная counter, которая предназначена только лишь для тела цикла for. И есть методы которые имеют побочный эффект, изменяя данные которые объявлены в другом блоке. Все это делает код совсем не идеальным, переносимым и делает его сложным в тестировании вне контекста в котором он описан.
Функциональный JavaScript
Объектно-ориентированный подход намного чище и модулярнее императивного, с которого мы начинали, но давайте посмотрим сможет ли мы сделать его еще лучше, исправив немного проблем о которых мы только что говорили. Было бы здорово, если бы мы смогли найти возможность использовать встроенные возможности JavaScript использовать функции как объекты одного класса, таким образом наш код стал бы чище, более страбилен и прост и изменении.
(function() {
  "use strict";
  var capify = function(str) {
    return [str.charAt(0).toUpperCase(), str.substring(1)].join("");
  };
  var processWords = function(fn, str) {
    return str.split(" ").map(fn).join(" ");
  };
  document.getElementById("main_button").addEventListener("click", function(e) {
    var something = prompt("Give me something to capitalize");
    alert(processWords(capify, something));
  });
}());
Вы обратили внимание насколько код стал короче? Мы определили только две функции capify  и processWords. Каждая из них простая, это значит, что она не зависит от контекста в котором выполняется. Функции не создают побочные эффекты, изменяющие значения других переменных. Функция может вернуть только одно значение для переданного набора аргументов. Благодаря этим изменениям, функции можно очень просто тестировать и использовать без изменений в любом другом месте.
Здесь есть только одно ключевое место, которые вы можете и не заметить если вы не использовали функциональный подход ранее. Речь идет о методе Array.map, который вызывается для каждого элемента временного массива, который мы создаем при разбитии строки. Эта функция – одна из нескольких удобных функций, которые мы получили, когда современные браузеры и серверные интерпретаторы JavaScript реализовали стандарты ECMAscript 5. Используя здесь map  вместо for, мы ликвидировали переменную counter и позволили сделать наш код чище.
Начинайте думать функционально.
Вы не должны выбрасывать все что написали до этого момента. Вы можете начать думать об использовании JavaScript в функциональном стиле, для это задавайте себе следующие вопросы:
 - Мои функции зависят от контекста, в котором они вызываются или они  могут быть независимыми?
 - Могу ли я написать код так, что я всегда буду получать один и тот же рузультат для одного набора входных данных?
 - Я уверен что мои функции не изменяет значения переменных извне?
 - Если я захочу использовать эти функеции в другом месте, нужно ли будет мне из изменять?
Это введение показывает лишь небольшую часть функциональной природы JavaScript, но я надеюсь, что возбудил ваш аппетит и вы изучите этот вопрос больше.