что такое сигнатура функции js
Функции
Зачастую нам надо повторять одно и то же действие во многих частях программы.
Например, необходимо красиво вывести сообщение при приветствии посетителя, при выходе посетителя с сайта, ещё где-нибудь.
Чтобы не повторять один и тот же код во многих местах, придуманы функции. Функции являются основными «строительными блоками» программы.
Объявление функции
Для создания функций мы можем использовать объявление функции.
Пример объявления функции:
Вызов showMessage() выполняет код функции. Здесь мы увидим сообщение дважды.
Этот пример явно демонстрирует одно из главных предназначений функций: избавление от дублирования кода.
Если понадобится поменять сообщение или способ его вывода – достаточно изменить его в одном месте: в функции, которая его выводит.
Локальные переменные
Переменные, объявленные внутри функции, видны только внутри этой функции.
Внешние переменные
У функции есть доступ к внешним переменным, например:
Функция обладает полным доступом к внешним переменным и может изменять их значение.
Внешняя переменная используется, только если внутри функции нет такой локальной.
Переменные, объявленные снаружи всех функций, такие как внешняя переменная userName в вышеприведённом коде – называются глобальными.
Глобальные переменные видимы для любой функции (если только их не перекрывают одноимённые локальные переменные).
Желательно сводить использование глобальных переменных к минимуму. В современном коде обычно мало или совсем нет глобальных переменных. Хотя они иногда полезны для хранения важнейших «общепроектовых» данных.
Параметры
Мы можем передать внутрь функции любую информацию, используя параметры (также называемые аргументами функции).
Параметры по умолчанию
Например, вышеупомянутая функция showMessage(from, text) может быть вызвана с одним аргументом:
Если мы хотим задать параметру text значение по умолчанию, мы должны указать его после = :
Теперь, если параметр text не указан, его значением будет «текст не добавлен»
В данном случае «текст не добавлен» это строка, но на её месте могло бы быть и более сложное выражение, которое бы вычислялось и присваивалось при отсутствии параметра. Например:
В JavaScript параметры по умолчанию вычисляются каждый раз, когда функция вызывается без соответствующего параметра.
Ранние версии JavaScript не поддерживали параметры по умолчанию. Поэтому существуют альтернативные способы, которые могут встречаться в старых скриптах.
Например, явная проверка на undefined :
…Или с помощью оператора || :
Возврат значения
Функция может вернуть результат, который будет передан в вызвавший её код.
Простейшим примером может служить функция сложения двух чисел:
Директива return может находиться в любом месте тела функции. Как только выполнение доходит до этого места, функция останавливается, и значение возвращается в вызвавший её код (присваивается переменной result выше).
Вызовов return может быть несколько, например:
Возможно использовать return и без значения. Это приведёт к немедленному выходу из функции.
Если функция не возвращает значения, это всё равно, как если бы она возвращала undefined :
Пустой return аналогичен return undefined :
Для длинного выражения в return может быть заманчиво разместить его на нескольких отдельных строках, например так:
И тогда всё сработает, как задумано.
Выбор имени функции
Функция – это действие. Поэтому имя функции обычно является глаголом. Оно должно быть простым, точным и описывать действие функции, чтобы программист, который будет читать код, получил верное представление о том, что делает функция.
Как правило, используются глагольные префиксы, обозначающие общий характер действия, после которых следует уточнение. Обычно в командах разработчиков действуют соглашения, касающиеся значений этих префиксов.
Например, функции, начинающиеся с «show» обычно что-то показывают.
Функции, начинающиеся с…
Примеры таких имён:
Благодаря префиксам, при первом взгляде на имя функции становится понятным что делает её код, и какое значение она может возвращать.
Функция должна делать только то, что явно подразумевается её названием. И это должно быть одним действием.
Два независимых действия обычно подразумевают две функции, даже если предполагается, что они будут вызываться вместе (в этом случае мы можем создать третью функцию, которая будет их вызывать).
Несколько примеров, которые нарушают это правило:
В этих примерах использовались общепринятые смыслы префиксов. Конечно, вы в команде можете договориться о других значениях, но обычно они мало отличаются от общепринятых. В любом случае вы и ваша команда должны точно понимать, что значит префикс, что функция с ним может делать, а чего не может.
Имена функций, которые используются очень часто, иногда делают сверхкороткими.
Это исключения. В основном имена функций должны быть в меру краткими и описательными.
Функции == Комментарии
Функции должны быть короткими и делать только что-то одно. Если это что-то большое, имеет смысл разбить функцию на несколько меньших. Иногда следовать этому правилу непросто, но это определённо хорошее правило.
Небольшие функции не только облегчают тестирование и отладку – само существование таких функций выполняет роль хороших комментариев!
Первый вариант использует метку nextPrime :
Второй вариант использует дополнительную функцию isPrime(n) для проверки на простое:
Второй вариант легче для понимания, не правда ли? Вместо куска кода мы видим название действия ( isPrime ). Иногда разработчики называют такой код самодокументируемым.
Таким образом, допустимо создавать функции, даже если мы не планируем повторно использовать их. Такие функции структурируют код и делают его более понятным.
Итого
Объявление функции имеет вид:
Для того, чтобы сделать код более чистым и понятным, рекомендуется использовать локальные переменные и параметры функций, не пользоваться внешними переменными.
Функция, которая получает параметры, работает с ними и затем возвращает результат, гораздо понятнее функции, вызываемой без параметров, но изменяющей внешние переменные, что чревато побочными эффектами.
Функции являются основными строительными блоками скриптов. Мы рассмотрели лишь основы функций в JavaScript, но уже сейчас можем создавать и использовать их. Это только начало пути. Мы будем неоднократно возвращаться к функциям и изучать их всё более и более глубоко.
Задачи
Обязателен ли «else»?
В ином случае она запрашивает подтверждение через confirm и возвращает его результат:
Есть ли хоть одно отличие в поведении этого варианта?
Оба варианта функций работают одинаково, отличий нет.
Перепишите функцию, используя оператор ‘?’ или ‘||’
В ином случае она задаёт вопрос confirm и возвращает его результат.
Сделайте два варианта функции checkAge :
Функции
Функции в JavaScript
Это определяет высокую выразительную мощность JavaScript и позволяет относить его к числу языков, реализующих функциональную парадигму программирования (что само по себе есть очень круто по многим соображениям).
Функция в JavaScript специальный тип объектов, позволяющий формализовать средствами языка определённую логику поведения и обработки данных.
Для понимания работы функций необходимо (и достаточно?) иметь представление о следующих моментах:
Объявление функций
Функции вида «function declaration statement»
Объявление функции (function definition, или function declaration, или function statement) состоит из ключевого слова function и следующих частей:
Например, следующий код объявляет простую функцию с именем square:
Функция square принимает один параметр, названный number. Состоит из одной инструкции, которая означает вернуть параметр этой функции (это number ) умноженный на самого себя. Инструкция return указывает на значение, которые будет возвращено функцией.
Примитивные параметры (например, число) передаются функции значением; значение передаётся в функцию, но если функция меняет значение параметра, это изменение не отразится глобально или после вызова функции.
Если вы передадите объект как параметр (не примитив, например, массив или определяемые пользователем объекты), и функция изменит свойство переданного в неё объекта, это изменение будет видно и вне функции, как показано в следующим примере:
Функции вида «function definition expression»
Функция вида «function declaration statement» по синтаксису является инструкцией (statement), ещё функция может быть вида «function definition expression». Такая функция может быть анонимной (она не имеет имени). Например, функция square может быть вызвана так:
Однако, имя может быть и присвоено для вызова самой себя внутри самой функции и для отладчика (debugger) для идентифицированные функции в стек-треках (stack traces; «trace» — «след» / «отпечаток»).
В следующим коде наша функция принимает функцию, которая является function definition expression, и выполняет его для каждого элемента принятого массива вторым аргументом.
Функция возвращает: [0, 1, 8, 125, 1000].
В JavaScript функция может быть объявлена с условием. Например, следующая функция будет присвоена переменной myFunc только, если num равно 0:
Метод — это функция, которая является свойством объекта. Узнать больше про объекты и методы можно по ссылке: Работа с объектами.
Вызовы функций
Объявление функции не выполняет её. Объявление функции просто называет функцию и указывает, что делать при вызове функции.
Эта инструкция вызывает функцию с аргументом 5. Функция вызывает свои инструкции и возвращает значение 25.
Функции могут быть в области видимости, когда они уже определены, но функции вида «function declaration statment» могут быть подняты (поднятие — hoisting), также как в этом примере:
Область видимости функции — функция, в котором она определена, или целая программа, если она объявлена по уровню выше.
Примечание: Это работает только тогда, когда объявлении функции использует вышеупомянутый синтаксис (т.е. function funcName()<> ). Код ниже не будет работать. Имеется в виду то, что поднятие функции работает только с function declaration и не работает с function expression.
Аргументы функции не ограничиваются строками и числами. Вы можете передавать целые объекты в функцию. Функция show_props() (объявленная в Работа с объектами) является примером функции, принимающей объекты аргументом.
Функция может вызвать саму себя. Например, вот функция рекурсивного вычисления факториала:
Затем вы можете вычислить факториалы от одного до пяти следующим образом:
Область видимости функций
Переменные объявленные в функции не могут быть доступными где-нибудь вне этой функции, поэтому переменные (которые нужны именно для функции) объявляют только в scope функции. При этом функция имеет доступ ко всем переменным и функциям, объявленным внутри её scope. Другими словами функция объявленная в глобальном scope имеет доступ ко всем переменным в глобальном scope. Функция объявленная внутри другой функции ещё имеет доступ и ко всем переменным её родительской функции и другим переменным, к которым эта родительская функция имеет доступ.
Scope и стек функции
Рекурсия
Функция может вызывать саму себя. Три способа такого вызова:
Для примера рассмотрим следующие функцию:
Внутри функции (function body) все следующие вызовы эквивалентны:
Функция, которая вызывает саму себя, называется рекурсивной функцией (recursive function). Получается, что рекурсия аналогична циклу (loop). Оба вызывают некоторый код несколько раз, и оба требуют условия (чтобы избежать бесконечного цикла, вернее бесконечной рекурсии). Например, следующий цикл:
можно было изменить на рекурсивную функцию и вызовом этой функции:
Однако некоторые алгоритмы не могут быть простыми повторяющимися циклами. Например, получение всех элементов структуры дерева (например, DOM) проще всего реализуется использованием рекурсии:
Также возможно превращение некоторых рекурсивных алгоритмов в нерекурсивные, но часто их логика очень сложна, и для этого потребуется использование стека (stack). По факту рекурсия использует stack: function stack.
Поведение стека можно увидеть в следующем примере:
Вложенные функции (nested functions) и замыкания (closures)
Вы можете вложить одну функцию в другую. Вложенная функция (nested function; inner) приватная (private) и она помещена в другую функцию (outer). Так образуется замыкание (closure). Closure — это выражение (обычно функция), которое может иметь свободные переменные вместе со средой, которая связывает эти переменные (что «закрывает» («close») выражение).
Поскольку вложенная функция это closure, это означает, что вложенная функция может «унаследовать» (inherit) аргументы и переменные функции, в которую та вложена. Другими словами, вложенная функция содержит scope внешней («outer») функции.
Следующий пример показывает вложенную функцию:
Поскольку вложенная функция формирует closure, вы можете вызвать внешнюю функцию и указать аргументы для обоих функций (для outer и innner).
Сохранение переменных
Это не отличается от хранения ссылок в других объектах, но часто менее очевидно, потому что не устанавливаются ссылки напрямую и нельзя посмотреть там.
Несколько уровней вложенности функций (Multiply-nested functions)
Функции можно вкладывать несколько раз, т.е. функция (A) хранит в себе функцию (B), которая хранит в себе функцию (C). Обе функции B и C формируют closures, так B имеет доступ к переменным и аргументам A, и C имеет такой же доступ к B. В добавок, поскольку C имеет такой доступ к B, который имеет такой же доступ к A, C ещё имеет такой же доступ к A. Таким образом closures может хранить в себе несколько scope; они рекурсивно хранят scope функций, содержащих его. Это называется chaining (chain — цепь; Почему названо «chaining» будет объяснено позже)
Рассмотрим следующий пример:
Конфликты имён (Name conflicts)
Когда два аргумента или переменных в scope у closure имеют одинаковые имена, происходит конфликт имени (name conflict). Более вложенный (more inner) scope имеет приоритет, так самый вложенный scope имеет наивысший приоритет, и наоборот. Это цепочка областей видимости (scope chain). Самым первым звеном является самый глубокий scope, и наоборот. Рассмотрим следующие:
Замыкания
Closures это один из главных особенностей JavaScript. JavaScript разрешает вложенность функций и предоставляет вложенной функции полный доступ ко всем переменным и функциям, объявленным внутри внешней функции (и другим переменным и функции, к которым имеет доступ эта внешняя функция).
Однако, внешняя функция не имеет доступа к переменным и функциям, объявленным во внутренней функции. Это обеспечивает своего рода инкапсуляцию для переменных внутри вложенной функции.
Также, поскольку вложенная функция имеет доступ к scope внешней функции, переменные и функции, объявленные во внешней функции, будет продолжать существовать и после её выполнения для вложенной функции, если на них и на неё сохранился доступ (имеется ввиду, что переменные, объявленные во внешней функции, сохраняются, только если внутренняя функция обращается к ним).
Closure создаётся, когда вложенная функция как-то стала доступной в неком scope вне внешней функции.
Более сложный пример представлен ниже. Объект с методами для манипуляции вложенной функции внешней функцией можно вернуть (return).
В коде выше переменная name внешней функции доступна для вложенной функции, и нет другого способа доступа к вложенным переменным кроме как через вложенную функцию. Вложенные переменные вложенной функции являются безопасными хранилищами для внешних аргументов и переменных. Они содержат «постоянные» и «инкапсулированные» данные для работы с ними вложенными функциями. Функции даже не должны присваиваться переменной или иметь имя.
Однако есть ряд подводных камней, которые следует учитывать при использовании замыканий. Если закрытая функция определяет переменную с тем же именем, что и имя переменной во внешней области, нет способа снова ссылаться на переменную во внешней области.
Использование объекта arguments
Объект arguments функции является псевдо-массивом. Внутри функции вы можете ссылаться к аргументам следующим образом:
Для примера рассмотрим функцию, которая конкатенирует несколько строк. Единственным формальным аргументом для функции будет строка, которая указывает символы, которые разделяют элементы для конкатенации. Функция определяется следующим образом:
Вы можете передавать любое количество аргументов в эту функцию, и он конкатенирует каждый аргумент в одну строку.
Рассмотрите объект Function в JavaScript-справочнике для большей информации.
Параметры функции
Начиная с ECMAScript 2015 появились два новых вида параметров: параметры по умолчанию (default parameters) и остаточные параметры (rest parameters).
Параметры по умолчанию (Default parameters)
С параметрами по умолчанию проверка наличия значения параметра в теле функции не нужна. Теперь вы можете просто указать значение по умолчанию для параметра b в объявлении функции:
Для более детального рассмотрения ознакомьтесь с параметрами по умолчанию.
Остаточные параметры (Rest parameters)
Остаточные параметры предоставляют нам массив неопределённых аргументов. В примере мы используем остаточные параметры, чтобы собрать аргументы с индексами со 2-го до последнего. Затем мы умножим каждый из них на значение первого аргумента. В этом примере используется стрелочная функция (Arrow functions), о которой будет рассказано в следующей секции.
Стрелочные функции
Более короткие функции
В некоторых функциональных паттернах приветствуется использование более коротких функций. Сравните:
Лексика this
До стрелочных функций каждая новая функция определяла своё значение this (новый объект в случае конструктора, undefined в strict mode, контекстный объект, если функция вызвана как метод объекта, и т.д.). Это оказалось раздражающим с точки зрения объектно-ориентированного стиля программирования.
В ECMAScript 3/5 эта проблема была исправлена путём присвоения значения this переменной, которую можно было бы замкнуть.
В arrow function значением this является окружающий его контекст, так следующий код работает ожидаемо:
Далее
Подробное техническое описание функций в статье справочника Функции
Смотрите также Function в Справочнике JavaScript для получения дополнительной информации по функции как объекту.
Подробнее о функциях
Тип функции в форме выражения (function type expressions)#
Простейшим способом описания типа функции является выражение. Такие типы похожи на стрелочные функции:
Название параметра является обязательным. Тип функции (string) => void означает «функция с параметром string типа any «!
Разумеется, для типа функции можно использовать синоним:
Сигнатуры вызова (call signatures)#
В JS функции, кроме того, что являются вызываемыми (callable), могут иметь свойства. Однако, тип-выражение не позволяет определять свойства функции. Для описания вызываемой сущности (entity), обладающей некоторыми свойствами, можно использовать сигнатуру вызова (call signature) в объектном типе:
Сигнатуры конструктора (construct signatures)#
Общие функции или функции-дженерики#
Часто тип данных, возвращаемых функцией, зависит от типа передаваемого функции аргумента или же два типа возвращаемых функцией значений зависят друг от друга. Рассмотрим функцию, возвращающую первый элемент массива:
В TS общие типы или дженерики (generics) используются для описания связи между двумя значениями. Это делается с помощью определения параметра Type в сигнатуре функции:
Добавив параметр Type и использовав его в двух местах, мы создали связь между входящими данными функции (массивом) и ее выходными данными (возвращаемым значением). Теперь при вызове функции возвращается более конкретный тип:
Предположение типа#
Мы можем использовать несколько параметров типа. Например, самописная версия функции map может выглядеть так:
Ограничения#
Ограничение, как следует из названия, используется для ограничения типов, принимаемых параметром типа.
Типы longerArr и longerStr были выведены на основе аргументов. Запомните, дженерики определяют связь между двумя и более значениями одного типа!
Работа с ограниченными значениями#
Вот пример распространенной ошибки, возникающей при работе с ограничениями дженериков:
Определение параметров типа#
Обычно, TS делает правильные выводы относительно типов аргументов в вызове дженерика, но так бывает не всегда. Допустим, мы реализовали такую функцию для объединения двух массивов:
При обычном вызове данной функции с несовпадающими по типу массивами возникает ошибка:
Руководство по написанию хороших функций-дженериков#
Используйте типы параметра без ограничений#
Рассмотрим две похожие функции:
Правило: по-возможности, используйте параметры типа без ограничений.
Используйте минимальное количество типов параметра#
Вот еще одна парочка похожих функций:
Правило: всегда используйте минимальное количество параметров типа.
Типы параметра должны указываться дважды#
Иногда мы забываем, что функция не обязательно должна быть дженериком:
Вот упрощенная версия данной функции:
Запомните, параметры типа предназначены для связывания типов нескольких значений.
Правило: если параметр типа появляется в сигнатуре функции только один раз, то, скорее всего, он вам не нужен.
Опциональные параметры#
Функции в JS могут принимать произвольное количество аргументов. Например, метод toFixed принимает опциональное количество цифр после запятой:
Мы также можем указать «дефолтный» параметр (параметр по умолчанию):
Опциональные параметры в функциях обратного вызова#
При написании функций, вызывающих «колбеки», легко допустить такую ошибку:
В действительности, это означает, что колбек может быть вызван с одним аргументом. Другими словами, определение функции говорит, что ее реализация может выглядеть так:
Поэтому попытка вызова такой функции приводит к ошибке:
В JS при вызове функции с большим (ударение на первый слог) количеством аргументов, чем указано в определении фукнции, дополнительные параметры просто игнорируются. TS ведет себя аналогичным образом. Функции с меньшим количеством параметров (одного типа) могут заменять функции с большим количеством параметров.
Правило: при написании типа функции для колбека, не указывайте опциональные параметры до тех пор, пока не будете вызывать функцию без передачи этих параметров.
Перегрузка функции (function overload)#
В TS такую функцию можно реализовать с помощью сигнатур перегрузки (overload signatures). Для этого перед телом функции указывается несколько ее сигнатур:
Функциональный JavaScript
Переменные
Пример с сменой состояния:
Объявление функций
Функции – это хлеб с маслом функционального программирования, и JavaScript частично поддерживает функциональный подход. Идея оборачивания куска программы и вызова её как переменной очень востребована. Это инструмент для структурирования больших программ, уменьшения повторений, назначения имён подпрограммам, и изолирование подпрограмм друг от друга.
Объявление функции сродное объявлению обычной переменной:
Здесь переменной square присваивается функция, которую можно вызвать в любом другом месте, передать ей параметр x и она вернёт квадрат этого значения. Прошу обратить внимание на то, что функция не меняет состояние никаких внешних переменных (чистая функция, без побочных эффектов), а также является детерминированной, т.е. для одинаковых входных данных будет возвращать одинаковый результат. Ещё один вариант объявления:
Разница в том, что в последнем случае присвоить переменной square новое значение нельзя, функцию можно использовать до объявления, а при отладке видно стек вызванных функций. Предпочтительнее использовать первый вариант так как периодически возникает необходимость подменять одну функцию на другую, с небольшим дублированием имени для удобства:
Вызов функций
В этого метода переменное число аргументов, при чем первый аргумент это контекст (будет описан ниже), а все последующие аргументы, которые будут переданы функции. В основном метод используется для изменения контекста функции.
Следующий метод apply очень похож на первый но с тем отличием что второй параметр должен содержать массив аргументов которые будут переданы в вызываемую функцию.
Есть ещё один метод, но он не совсем связан з вызовом функций, скорее с созданием новой функции. Именуется этот метод bind и он также позволяет «мертво» привязать контекст и параметры к функции. Если указаны параметры — они будут прибавлены к каждому вызову новой функции, причём встанут перед теми, которые указаны при вызове. Например:
В даном примере видно как при помощи метода bind была создана новая функция, привязанная к null объекту и также продемонстрировано, как можно сделать частичную передачу параметров. Т.е. привязали аргумент 1 к функции.
Еще один довольно часто используемый фокус, когда функция сразу же после обьявления исполняется. Это позволяет скрыть переменные и логику которые не нужны в других частях программы, а также создавать объекты, в которые имеют собственные «приватные» переменные/методы:
Контекст
В языке Си есть такое понятие как структуры ( struct ) они удобны тем, что позволяют из простых типов создать сложную структуру с полями, через которые можно обратится к значению.
Программисты довольно быстро поняли, что таким способом довольно удобно обеднять связанные значения. Вот только было сложно проследить кто меняет поля структуры (нечто похожее на глобальные переменные). В связи с этим решили структуры и функции, которые с ними работают, помещать в один файл. Стало более удобно, но надо было следить за тем чтобы кто-то случайно не изменил состояния переменных в структуре, а писать функции, вроде этой, не очень удобно:
Вызов функции которая определена в классе, делается примерно так же как и обращение к значению поля. Если очень по-простому то контекст функции будет равным тому, что идёт до точки перед именем функции. А теперь фокус:
Или же есть возможность вообще создать функцию и потом применять её в контексте любого объекта. Здесь, кстати, метод bind исполняется в контексте функции setName поэтому он знает к которой функции привязывать контекст.
В примере так же продемонстрировано, что результат, который возвращает функция, можно использовать сразу. Т.е. в данном примере метод bind возвращает новую функцию, которая сразу же вызывается. Аналогично можно делать с объектами, если функция возвращает объект можно сразу его использовать, как собственно сделано в jQuery. Данный подход называется цепочка вызовов (chaining) и бывает очень полезен, но в функциональном подходе его заменяет композиция.
Каррирование и частичное применение
Каррирование это приём в функциональном программировании, позволяющий преобразовать функцию, заменив её несколько первых аргументов константными значениями, тем самым создав новую функцию с меньшим количеством аргументов на основе старой. Этот удобно применять в случае, когда первые несколько аргументов функции заранее известны, и указывать их при каждом вызове нет необходимости. Для краткости будем называть преобразовываемую функцию каррируемой, а функцию, которая её преобразовывает, каррирующей. Классический пример с суммированием.
Что делать, если нам часто надо суммировать одно число к другому, и при том одно число всегда одно и то же? Нужно где то запомнить это значение. Один из вариантов это использовать замыкание и сделать так:
Может быть довольно таки удобно, особенно учитывая то, что в функциональном стиле проще оперировать з функциями, принимающими одно значение и возвращающее один результат. С помощью каррирования можно построить функцию с необходимыми значениями, и которая только ожидает последний аргумент.
В заголовке упомянуты два термина: карирование и частичное применение, они немного похожи, но все же отличаются. Частичное применение это когда у нас есть функция, которая ожидает несколько параметров, но первые параметры нам известны и мы их применяем, например с помощью метода bind :
Теперь при вызове функции sum3 и передачи ей остаточных параметров будет выполнено суммирование. Здесь таится отличие от каррирования если вызвать sumBase3 с одним аргументом, то суммирование будет выполнено с ошибкой, по той причине, что последний аргумент не был передан. В случае с карированием такого не произойдёт. Карированая функция так же ожидает всех аргументов, только вот суммирование не будет вызвано до тех пор, пока все аргументы не будут получены:
Вот таким вон нехитрым способом можно уменьшить количество параметров в функциях.
Сигнатура функций
В то время, как JavaScript является языком с динамической типизацией, нет необходимости указывать тип переменной. Это позволяет писать более абстрактный код, хотя временами трудно понять, что ожидает функция и что возвращает. Иногда это приводит к «неожиданным» результатам. Для того, чтобы помочь программисту быстрее ориентироваться в функциях есть несколько подходов, как описать сигнатуры функций. Ах, да, сигнатура функций это как бы описание того что функция получает (аргументы) и то что возвращает.
Первый и самый не интересный нам подход, который используется в ActionScript (часть семейства ECMAScript). В нем используется типизация и параметры описываются более явно, пример:
Плюс такого подхода в том, что транслятор может проверять типы и «бить в колокол», если что то не так, но с другой стороны у нас уменьшается абстракция, и мы должны писать ещё одну функцию в случае если немного нужно изменить сигнатуру. в общем, не гибко.
Следующий, более JavaScript подход, представляет JSDoc. Подход более лучший, так как позволят не только описать сигнатуру функций, но и дополнительную информацию, на основании которой можно сгенерировать документацию. Более того, некоторые IDE умеют читать этот формат и использовать для подсказок при разработке, что довольно удобно. Основной минус так это то, что он довольно многословен, и иногда читать смесь из документации и кода не очень-то и удобно. Пример:
И последний вариант, на котором и остановимся. Предназначен он для описания только самых сигнатур и не более. Не многословен, и нет поддержки в IDE (пока нет). Начнём с того, что он «украден» в правильных функциональных языков где каррирование у всех функций по умолчанию. Поэтому далее будем рассматривать все функции как каррированые. Пример сигнатуры:
Как видно с примера, функция работает с четырьмя параметрами первые три это собственно входные параметры, а четвёртый это результат работы. Ах, да важное замечание: в функциональных языках функции всегда возвращают что-нибудь. Если функция ничего не возвращает, то по факту она ничего не делает. и да, не забываем что в идеале функции чистые, т.е. ничего не изменяют (DOM, ввод-вывод, AJAX и т.п.). Функции также не должны изменять значения, которые переданы как аргументы. Если все же есть необходимость их изменить, тогда нужно создать копию оригинального значения и изменить его. Да, это немного расточительно по отошению к памяти, но более безопасно. Ещё пример:
Более сложный пример:
Композиция
Композиция — процесс применения одной функции к результату другой.
Нечто похожее существует уже довольно давно в *nix системах и называется конвеером (pipe).
Аналогично работает функция compose : переданные ей функции она сохраняет в массиве и возвращает новую функцию, и при её вызове будет выполняться последняя функция из переданных в compose и ей же будут переданные аргументы. В свою очередь результат работы последней функции будет передан предпоследней. И так до самой первой функции, результат которой будет возвращён. С помощью такого подхода можно необходимую логику декомпозировать (розбить на более мелкие части), в результате у нас будет увеличено переиспользование кода, а значит багам будет сложнее скрыться и разработка буте идти быстрее.
Очень важно отметить, что композиция/декомпозиция способствует более абстрактному коду. Абстракция в свою очередь способствует быстрой разработке, (но!) увеличивает время на обучение. Другими словами менее опытные разработчики будут тратить больше времени на обучение.
Функтор
И вот последний еще более приземленный пример:
Построитель вычислений aka «Монады»
Hey Underscore, You’re Doing It Wrong!
В этом и кроется проблема. Здесь нам нужна переменная для сохранения данных, которые будут обработаны дальше. Что если мы сделаем функцию следующего вида:
Первое что нужно отметить мы сделали её каррированой и второе поменяли местами аргументы. И если теперь сделаем так:
Более того с таким подходом можно делать и асинхронные операции:
Промисы? Колбэки? Асинхронный код? Не, не слышал. Но вот тут есть небольшая проблема: а что если сервер недоступен? Или данные должен был ввести пользователь, а их нет? Ой, беда.
Maybe
Хотя если подумать не такая уже и беда, все можно решить в функциональном стиле. Для этого в нас есть (будут?) монады! Посмотрим, как можно определить монаду maybe:
Either
Напишем короткую функцию для демонстрации:
Обращаю внимание на то, что теперь метод getElement вынесен из композиции, так как ему на вход нужно «чистое» значение, а не монада, и возвращает он монаду.