Статьи

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

Эта статья включена в нашу антологию, современный JavaScript . Если вы хотите, чтобы все в одном месте было в курсе современного JavaScript, зарегистрируйтесь в SitePoint Premium и загрузите себе копию.

Вы слышали, что 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. Имена функций не очень описательны, и это частично объясняется тем фактом, что все зависит от контекста, который может существовать или не существовать. Но если вам довелось запустить это в браузере внутри HTML-документа, который определил <button id="main_button"> , вам может быть предложено <button id="main_button"> текст для работы, а затем увидеть alert с первой буквой каждого из слова в этом тексте с большой буквы.

Подобный императивный код написан так, чтобы его можно было читать и выполнять сверху вниз (поднять или взять небольшую переменную ). Но есть некоторые улучшения, которые мы могли бы сделать, чтобы очистить его и сделать его более читабельным, используя преимущества объектно-ориентированной природы JavaScript.

Объектно-ориентированный JavaScript

Через несколько лет разработчики начали замечать проблемы с императивным кодированием в общей среде, такой как браузер. Глобальные переменные из одного фрагмента 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 или более раннюю версию? Сценарий, подобный этому, вероятно, будет вставлен в конец элемента <body> в HTML-документе, чтобы убедиться, что весь DOM был загружен до его обработки, поэтому <button> на который он полагается, будет доступен.

Но, несмотря на всю эту реконфигурацию, все еще есть много артефактов того же императивного стиля, которые привели нас сюда. Методы в функции конструктора полагаются на переменные, относящиеся к родительскому объекту. Существует циклическая конструкция для перебора всех элементов массива строк. Существует переменная- 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 . Каждая из этих функций является чистой, то есть они не зависят от состояния кода, из которого они вызваны. Функции не создают побочных эффектов, которые изменяют переменные вне себя. Существует один и только один результат, который функция возвращает для любого заданного набора аргументов. Из-за этих улучшений новые функции очень легко тестировать, и их можно извлечь прямо из этого кода и использовать в другом месте без каких-либо изменений.

Там могло быть одно ключевое слово, которое вы бы не узнали, если бы не заглянули в какой-то функциональный код раньше. Мы воспользовались новым методом map в Array чтобы применить функцию к каждому элементу временного массива, который мы создали, когда мы разбивали нашу строку. Map — лишь один из нескольких удобных методов, которые нам дали, когда современные браузеры и серверные интерпретаторы JavaScript реализовали стандарты ECMAscript 5. Использование здесь map вместо цикла for исключило переменную counter и помогло сделать наш код намного чище и легче для чтения.

Начните думать функционально

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

  • Мои функции зависят от контекста, в котором они вызываются, или они чисты и независимы?
  • Могу ли я написать эти функции таким образом, чтобы я мог полагаться на то, что они всегда возвращают один и тот же результат для заданного ввода?
  • Я уверен, что мои функции ничего не изменяют вне себя?
  • Если бы я хотел использовать эти функции в другой программе, нужно ли мне вносить в них изменения?

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