Что такое функции почему они полезны

Что такое функции почему они полезны thumbnail

  Обновл. 1 Июл 2020  | 

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

Зачем использовать функции?

Начинающие программисты часто спрашивают: «А можно ли обходиться без функций и весь код помещать непосредственно в функцию main()?». Если вашего кода всего 10-20 строчек, то можно. Если же серьезно, то функции предназначены для упрощения кода, а не для его усложнения. Они имеют ряд преимуществ, которые делают их чрезвычайно полезными в нетривиальных программах.

   Структура. Как только программы увеличиваются в размере/сложности, сохранять весь код внутри main() становится трудно. Функция — это как мини-программа, которую мы можем записать отдельно от головной программы, не заморачиваясь при этом об остальных частях кода. Это позволяет разбивать сложные задачи на более мелкие и простые, что кардинально снижает общую сложность программы.

   Повторное использование. После объявления функции, её можно вызывать много раз. Это позволяет избежать дублирования кода и сводит к минимуму вероятность возникновения ошибок при копировании/вставке кода. Функции также могут использоваться и в других программах, уменьшая объем кода, который нужно писать с нуля каждый раз.

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

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

   Абстракция. Для того, чтобы использовать функцию, нам нужно знать её имя, данные ввода, данные вывода и где эта функция находится. Нам не нужно знать, как она работает. Это очень полезно для написания кода, понятного другим (например, Стандартная библиотека С++ и всё, что в ней находится, создана по этому принципу).

Каждый раз, при вызове std::cin или std::cout для ввода или вывода данных, мы используем функцию из Стандартной библиотеки C++, которая соответствует всем указанным выше концепциям.

Эффективное использование функций

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

   Рекомендация №1: Код, который появляется более одного раза в программе, лучше переписать в виде функции. Например, если мы получаем данные от пользователя несколько раз одним и тем же способом, то это отличный вариант для написания отдельной функции.

  Рекомендация №2: Код, который используется для сортировки чего-либо, лучше записать в виде отдельной функции. Например, если у нас есть список вещей, которые нужно отсортировать — пишем функцию сортировки, куда передаем несортированный список и откуда получаем отсортированный.

  Рекомендация №3: Функция должна выполнять одно (и только одно) задание.

  Рекомендация №4: Когда функция становится слишком большой, сложной или непонятной — её следует разбить на несколько подфункций. Это называется рефакторингом кода.

При изучении C++ вам предстоит написать много программ, которые будут включать следующие три подзадания:

   Получение данных от пользователя.

   Обработка данных.

   Вывод результата.

Для простых программ (менее, чем 30 строчек кода) частично или все эти три подзадания можно записать в функции main(). Для более сложных программ (или просто для практики) каждое из этих трех подзаданий является хорошим вариантом, чтобы написать отдельные функции.

Новички часто комбинируют обработку ввода и вывод результата в одной функции. Тем не менее, это нарушает правило «одного задания». Функция, которая обрабатывает значение, должна возвращать его в caller, а дальше уже пускай caller сам решает, что ему с ним делать.

Оценить статью:

Загрузка…

Источник

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

Если вы не совсем пони­ма­е­те, что такое функ­ция и зачем она нуж­на — доб­ро пожа­ло­вать в наш кат:

Что такое функция

Функ­ция — это мини-программа внут­ри вашей основ­ной про­грам­мы, кото­рая дела­ет какую-то одну понят­ную вещь. Вы одна­жды опи­сы­ва­е­те, что это за вещь, а потом ссы­ла­е­тесь на это опи­са­ние.

Напри­мер, вы пише­те игру. Каж­дый раз, когда игрок попа­да­ет в цель, уби­ва­ет вра­га, дела­ет ком­бо, закан­чи­ва­ет уро­вень или пада­ет в лаву, вам нуж­но доба­вить или уба­вить ему очков. Это дела­ет­ся дву­мя дей­стви­я­ми: к ста­рым очкам добав­ля­ют­ся новые, на экран выво­дит­ся новая сум­ма очков. Допу­стим, эти дей­ствия зани­ма­ют 8 строк кода.

Допу­стим, в игре есть 100 ситу­а­ций, когда нуж­но доба­вить или уба­вить очки — для каж­до­го типа вра­га, пре­гра­ды, уров­ня и т. д. Что­бы в каж­дой из ста ситу­а­ций не писать одни и те же восемь строк кода, вы упа­ко­вы­ва­е­те эти восемь строк в функ­цию. И теперь в ста местах вы пише­те одну стро­ку: напри­мер, changeScore(10) — чис­ло очков повы­сит­ся на 10.

Если теперь изме­нить, что про­ис­хо­дит в функ­ции changeScore(), то изме­не­ния отра­зят­ся как бы во всех ста местах, где эта функ­ция вызы­ва­ет­ся.

Зачем нужны функции

Функ­ции нуж­ны, что­бы замет­но упро­щать и сокра­щать код, адап­ти­ро­вать его для раз­ных плат­форм, делать более отка­зо­устой­чи­вым, лег­ко отла­жи­вать. И вооб­ще поря­док в функ­ци­ях — поря­док в голо­ве.

Возь­мём тот же при­мер с под­счё­том очков. Что если при добав­ле­нии очков нуж­но не толь­ко выво­дить их на экран, но и запи­сы­вать в файл? Про­сто добав­ля­е­те в опре­де­ле­нии функ­ции допол­ни­тель­ные коман­ды, свя­зан­ные с фай­ла­ми, и они теперь будут испол­нять­ся каж­дый раз, когда функ­цию сно­ва вызо­вут в основ­ной про­грам­ме. Не нуж­но пол­зать по все­му коду, искать места с добав­ле­ни­ем очков и допи­сы­вать там про фай­лы. Мень­ше руч­но­го тру­да, мень­ше опе­ча­ток, мень­ше неза­кры­тых ско­бок.

А что если нуж­но не толь­ко писать очки в файл, но и сле­дить за рекор­дом? Пишем новую функ­цию getHighScore(), кото­рая доста­ёт откуда-то рекорд по игре, и две дру­гие — setHighScore() и celebrateHighScore() — одна будет пере­за­пи­сы­вать рекорд, если мы его поби­ли, а вто­рая — как-то поздрав­лять поль­зо­ва­те­ля с рекор­дом.

language: JavaScript

// Объявляем новую функцию. Она будет называться changeScore и принимать один аргумент, который мы для этого фрагмента назовём howMuch. Дальше мы просто будем подавать в функцию число.

function changeScore(howMuch){

   // Прибавим к старым очкам новые

   playerScore = playerScore + howMuch;

   // Выведем новые очки на экран

   $(‘#scoretext’).text(playerScore)

   // Узнаем, какой у нас рекорд. Для этого объявим новую переменную highScore, вызовем функцию getHighScore(), запишем её результат в эту переменную

   var highScore = getHighScore();

   // А теперь сравним, больше ли наши очки, чем рекорд по игре

   if(playerScore > highScore){

       //Рекорд побит, значит, надо его записать

       setHighScore(playerScore, playerName);

       // Делаем тут что-то, что обычно делают, когда ты побил рекорд игры. Фейерверки? Музыка? Мигание рекордных очков на экране? Мы не знаем пока, что именно будет делать эта функция, и нам это сейчас неважно

       celebrateHighScore();

   }

}

Что такое функции почему они полезны
Ско­пи­ро­вать код

Код ско­пи­ро­ван

Теперь при каж­дом сра­ба­ты­ва­нии changeScore() будет вызы­вать все осталь­ные функ­ции. И сколь­ко бы раз мы ни вызва­ли в коде changeScore(), она потя­нет за собой всё хозяй­ство авто­ма­ти­че­ски.

Читайте также:  Рекультивация земель в местах добычи полезных

Сила ещё в том, что при раз­бо­ре этой функ­ции нам неваж­но, как реа­ли­зо­ва­ны getHighScore(), setHighScore() и celebrateHighScore(). Они зада­ют­ся где-то в дру­гом месте кода и в дан­ный момент нас не вол­ну­ют. Они могут брать дан­ные с жёст­ко­го дис­ка, писать их в базу дан­ных, изда­вать зву­ки и взла­мы­вать Пен­та­гон — это будет рас­пи­са­но внут­ри самих функ­ций в дру­гих местах тек­ста.

Без функ­ций труд­но пове­сить дей­ствия на какие-либо кноп­ки в интер­фей­се. Напри­мер, у вас на сай­те есть фор­ма, и при кли­ке на кноп­ку «Отпра­вить» вы хоти­те про­ве­рять, что дан­ные в фор­ме пра­виль­но вве­де­ны. Вы спо­кой­но опи­сы­ва­е­те где-то в коде функ­цию validateForm() и веша­е­те её на нажа­тие кноп­ки. Кноп­ку нажа­ли — функ­ция вызва­лась. Не нуж­но впи­сы­вать в кноп­ку пол­ный текст про­грам­мы.

А без функ­ции при­шлось бы писать огром­ную программу-валидатор пря­мо внут­ри кноп­ки. Это испол­ни­мо, но код выгля­дел бы страш­но гро­мозд­ким. Что если у вас на стра­ни­це три фор­мы, и каж­дую нуж­но вали­ди­ро­вать?

Хоро­шо напи­сан­ные функ­ции рез­ко повы­ша­ют чита­е­мость кода. Мы можем читать чужую про­грам­му, уви­деть там функ­цию getExamScore(username) и знать, что послед­няя каким-то обра­зом выяс­ня­ет резуль­та­ты экза­ме­на по такому-то юзер­ней­му. Как она там устро­е­на внут­ри, куда обра­ща­ет­ся и что исполь­зу­ет — вам неваж­но. Для нас это как бы одна про­стая понят­ная коман­да.

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

Функ­ции — это бес­ко­неч­ная радость. На этом наш экс­курс в функ­ции закон­чен, пере­хо­дим к чисто­те.

Что такое чистые функции

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

Вот при­ме­ры.

Один и тот же результат

Допу­стим, мы при­ду­ма­ли функ­цию, кото­рая счи­та­ет пло­щадь кру­га по его ради­у­су: getCircleArea(). Для наших целей мы берём чис­ло пи, рав­ное 3,1415, и впи­сы­ва­ем в функ­цию:

language: JavaScript

function getCircleArea(radius){

   // Задаём наше местное определение числа пи

   var localPi = 3.1415;

   // Считаем площадь: пи на радиус в квадрате. Это то же самое, что пи умножить на радиус и ещё раз умножить на радиус

   var circleArea = localPi*radius*radius;

   // Говорим функции вернуть то, что мы сейчас рассчитали

   return circleArea;

}

Что такое функции почему они полезны
Ско­пи­ро­вать код

Код ско­пи­ро­ван

Теперь этой функ­ции надо скор­мить чис­ло, и она выдаст пло­щадь кру­га:

  • getCircleArea(2) все­гда выдаст резуль­тат 12,6060
  • getCircleArea(4) все­гда выдаст 50,2640

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

Дру­гой при­мер. Мы пишем программу-таймер, кото­рая долж­на издать звук, напри­мер, за 10 секунд до кон­ца отве­дён­но­го ей вре­ме­ни. Что­бы узнать, сколь­ко оста­лось секунд, нам нуж­на функ­ция: она выяс­ня­ет коли­че­ство секунд меж­ду дву­мя отмет­ка­ми вре­ме­ни. Мы даём ей два вре­ме­ни в каком-то фор­ма­те, а функ­ция сама неким обра­зом высчи­ты­ва­ет, сколь­ко меж­ду ними секунд. Как имен­но она это счи­та­ет, сей­час неваж­но. Важ­но, что она это дела­ет оди­на­ко­во. Это тоже функ­ция с пред­ска­зу­е­мым резуль­та­том:

  • getInterval(’09:00:00′, ’09:00:12′) все­гда выдаст 12
  • getInterval(’09:00:00′, ’21:00:00′) все­гда выдаст 43 200

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

  • getSecondsTo(’23:59:59′) в один момент даст 43 293 секун­ды,
  • а спу­стя 2 мину­ты эта же функ­ция getSecondsTo(’23:59:59′) даст 43 173 секун­ды.

Это функ­ция с непред­ска­зу­е­мым резуль­та­том. У неё есть непред­ска­зу­е­мая зави­си­мость, кото­рая может повли­ять на рабо­ту про­грам­мы — зави­си­мость от теку­ще­го вре­ме­ни на ком­пью­те­ре. Что если во вре­мя испол­не­ния у поль­зо­ва­те­ля обну­ли­лись часы? Или он сме­нил часо­вой пояс? Или при запро­се теку­ще­го вре­ме­ни про­ис­хо­дит ошиб­ка? Или его ком­пью­тер не под­дер­жи­ва­ет отда­чу вре­ме­ни?

С точ­ки зре­ния чистых функ­ций, пра­виль­нее будет сна­ча­ла отдель­ны­ми функ­ци­я­ми полу­чить все внеш­ние зави­си­мо­сти, про­ве­рить их и убе­дить­ся, что они под­хо­дят для нашей рабо­ты. И потом уже вызвать функ­цию с под­счё­том интер­ва­лов. Что-то вро­де тако­го:

  • var now = getCurrentTime();
  • var interval = getInterval(now, ’23:59:59′);

Тогда в функ­ции getCurrentTime() мож­но будет про­пи­сать всё хозяй­ство, свя­зан­ное с полу­че­ни­ем нуж­но­го вре­ме­ни и его про­вер­кой, а в getInterval() оста­вить толь­ко алго­ритм, кото­рый счи­та­ет раз­ни­цу во вре­ме­ни.

Побочные эффекты

Совре­мен­ные язы­ки про­грам­ми­ро­ва­ния поз­во­ля­ют функ­ци­ям рабо­тать не толь­ко внут­ри себя, но и вли­ять на окру­же­ние. Напри­мер, функ­ция может выве­сти что-то на экран, запи­сать на диск, изме­нить какую-то гло­баль­ную пере­мен­ную. Взло­мать Пен­та­гон, опять же. Всё это назы­ва­ет­ся побоч­ны­ми эффек­та­ми. Хоро­шие про­грам­ми­сты смот­рят на них крайне насто­ро­жен­но.

При­мер­чи­ки!

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

Мож­но напи­сать функ­цию, кото­рая счи­ты­ва­ет все зада­чи из памя­ти, нахо­дит нуж­ные и воз­вра­ща­ет. При этом на зада­чи в памя­ти это не вли­я­ет: они как были сва­ле­ны в кучу, так и оста­лись. Это функ­ция без побоч­ных эффек­тов.

  • getTasksByPriority(‘high’) — вер­нёт новый мас­сив с при­о­ри­тет­ны­ми зада­ча­ми, не изме­нив дру­гие мас­си­вы. В памя­ти был один мас­сив, а теперь появит­ся ещё и вто­рой.

А мож­но напи­сать функ­цию, кото­рая счи­ты­ва­ет зада­чи, нахо­дит нуж­ные, сти­ра­ет их из исход­но­го места и запи­сы­ва­ет в какое-то новое — напри­мер, в отдель­ный мас­сив при­о­ри­тет­ных задач. Полу­ча­ет­ся, буд­то она физи­че­ски вытя­ну­ла нуж­ные зада­чи из исход­но­го мас­си­ва. Побоч­ный эффект этой функ­ции — изме­не­ние исход­но­го мас­си­ва задач в памя­ти.

  • pullTasksByPriority(‘high’) — физи­че­ски выта­щит зада­чи из исход­но­го мас­си­ва и пере­ме­стит их в какой-то новый. В ста­ром мас­си­ве умень­шит­ся чис­ло задач.
  • Такие изме­не­ния назы­ва­ют мута­ци­я­ми: я вызвал функ­цию в одном месте, а мути­ро­ва­ло что-то в дру­гом.

Про­грам­ми­сты насто­ро­жен­но отно­сят­ся к мута­ци­ям, пото­му что за ними слож­но сле­дить. Что если из-за какой-то ошиб­ки функ­ции выпол­нят­ся в непра­виль­ном поряд­ке и уни­что­жат важ­ные для про­грам­мы дан­ные? Или функ­ция выпол­нит­ся непред­ска­зу­е­мо мно­го раз? Или она застря­нет в цик­ле и из-за мута­ций разо­рвёт память? Или мута­ция про­изой­дёт не с тем кус­ком про­грам­мы, кото­рый мы изна­чаль­но хоте­ли?

Вот типич­ная ошиб­ка, свя­зан­ная с мута­ци­ей. Мы пишем игру, нуж­но поме­нять сум­му игро­вых очков. За это отве­ча­ет функ­ция changeScore(), кото­рая запи­сы­ва­ет резуль­тат в гло­баль­ную пере­мен­ную playerScore — то есть мути­ру­ет эту пере­мен­ную. Мы слу­чай­но, по невни­ма­тель­но­сти, вызва­ли эту функ­цию в двух местах вме­сто одно­го, и бал­лы уве­ли­чи­ва­ют­ся вдвое. Это баг.

Дру­гая типич­ная ошиб­ка. Про­грам­мист напи­сал функ­цию, кото­рая уда­ля­ет из таб­ли­цы послед­нюю стро­ку, пото­му что был почему-то уве­рен: стро­ка будет пустой и нико­му не нуж­ной. Слу­чай­но эта функ­ция вызы­ва­ет­ся в бес­ко­неч­ном цик­ле и сти­ра­ет все стро­ки, от послед­ней к пер­вой. Дан­ные уни­что­жа­ют­ся. А вот если бы функ­ция не уда­ля­ла стро­ку из таб­ли­цы, а дела­ла новую таб­ли­цу без послед­ней стро­ки, дан­ные бы не постра­да­ли.

Без мути­ру­ю­щих функ­ций, конеч­но, мы не обой­дём­ся — нуж­но и выво­дить на экран, и писать в файл, и рабо­тать с гло­баль­ны­ми пере­мен­ны­ми. Слож­но пред­ста­вить про­грам­му, в кото­рой вооб­ще не будет мути­ру­ю­щих функ­ций. Но про­грам­ми­сты пред­по­чи­та­ют выде­лять такие функ­ции отдель­но, тести­ро­вать их осо­бо тща­тель­но, и вни­ма­тель­но сле­дить за тем, как они рабо­та­ют. Гру­бо гово­ря, если функ­ция вно­сит изме­не­ния в боль­шой важ­ный файл, она долж­на как мини­мум про­ве­рить кор­рект­ность вхо­дя­щих дан­ных и сохра­нить резерв­ную копию это­го фай­ла.

Как этим пользоваться

Когда буде­те писать свою сле­ду­ю­щую функ­цию, задай­тесь вопро­са­ми:

  1. Нет ли тут каких-то зави­си­мо­стей, кото­рые могут пове­сти себя непред­ска­зу­е­мо? Не беру ли я дан­ные неиз­вест­но отку­да? Что если все эти дан­ные у меня не возь­мут­ся или ока­жут­ся не тем, что мне надо? Как защи­тить про­грам­му на слу­чай, если этих дан­ных там не ока­жет­ся?
  2. Вли­я­ет ли эта функ­ция на дан­ные за её пре­де­ла­ми?

И если логи­ка про­грам­мы поз­во­ля­ет, поста­рай­тесь сде­лать так, что­бы функ­ция ни от чего не зави­се­ла и ни на что за сво­и­ми пре­де­ла­ми не вли­я­ла. Тогда код будет более чита­е­мым, а коллеги-программисты сра­зу уви­дят, что перед ними вдум­чи­вый раз­ра­бот­чик.

Практику веб‑программирования можно получить в Яндекс.Практикуме

Читайте также:  Чем полезен куриный бульон для женщин

Первый курс — бесплатно.

Попробовать

Что такое функции почему они полезныЧто такое функции почему они полезныЧто такое функции почему они полезныЧто такое функции почему они полезны

Источник

«Функции высшего порядка» — это одна из тех фраз, которыми часто разбрасываются. Но редко кто может остановиться и объяснить, что это такое. Возможно, вы уже знаете, что называют функциями высшего порядка. Но как мы используем их в реальных проектах? Когда и почему они бывают полезны? Можем ли мы с их помощью манипулировать DOM? Или люди, которые используют эти функции, просто хвастаются? Быть может, они бессмысленно усложняют код?

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

Функции как объекты первого класса

В JavaScript есть не меньше трёх способов (всего их больше) написать новую функцию. Во-первых, можно написать объявление функции:

// Take a DOM element and wrap it in a list item element.
function itemise(el) {
const li = document.createElement(‘li’);
li.appendChild(el);
return li;
}

Надеюсь, вам всё понятно. Также вы, вероятно, знаете, что можно написать выражение функции:

const itemise = function(el) {
const li = document.createElement(‘li’);
li.appendChild(el);
return li;
}

И наконец, есть ещё один способ написать ту же функцию — в качестве стрелочной функции:

const itemise = (el) => {
const li = document.createElement(‘li’);
li.appendChild(el);
return li;
}

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

Но обратите внимание, что последние два примера присваивают функцию переменной. Это выглядит мелочью. Почему бы и не присвоить функцию переменной? Но это очень важно. Функции в JavaScript относятся к «первому классу». Поэтому мы можем:

  • Присваивать функции переменным.
  • Передавать функции в качестве аргументов другим функциям.
  • Возвращать функции из других функций.

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

Мы увидели присвоение функций переменным. А что насчёт передачи их в качестве параметров? Давайте напишем функцию, которую можно использовать с DOM-элементами. Если выполнить document.querySelectorAll(), то в ответ получим не массив, а NodeList. У NodeList нет метода .map(), как у массивов, поэтому напишем так:

// Apply a given function to every item in a NodeList and return an array.
function elListMap(transform, list) {
// list might be a NodeList, which doesn’t have .map(), so we convert
// it to an array.
return […list].map(transform);
}

// Grab all the spans on the page with the class ‘for-listing’.
const mySpans = document.querySelectorAll(‘span.for-listing’);

// Wrap each one inside an <li> element. We re-use the
// itemise() function from earlier.
const wrappedList = elListMap(itemise, mySpans);

Здесь мы передаём функцию itemise в качестве аргумента функции elListMap. Но можем использовать elListMap не только для создания списков. К примеру, с её помощью можно добавить класс в набор элементов:

function addSpinnerClass(el) {
el.classList.add(‘spinner’);
return el;
}

// Find all the buttons with class ‘loader’
const loadButtons = document.querySelectorAll(‘button.loader’);

// Add the spinner class to all the buttons we found.
elListMap(addSpinnerClass, loadButtons);

elLlistMap берёт другую функцию в качестве параметра и преобразует. То есть мы можем использовать elListMap для решения разных задач.

Мы рассмотрели пример передачи функций в качестве параметров. Теперь поговорим о возврате функции из функции. Как это выглядит?

Сначала напишем обычную старую функцию. Нам нужно взять список элементов li и обернуть в ul. Легко:

function wrapWithUl(children) {
const ul = document.createElement(‘ul’);
return […children].reduce((listEl, child) => {
listEl.appendChild(child);
return listEl;
}, ul);
}

А если потом у нас будет куча элементов-параграфов, которые нам захочется обернуть в div? Без проблем, напишем для этого ещё одну функцию:

function wrapWithDiv(children) {
const div = document.createElement(‘div’);
return […children].reduce((divEl, child) => {
divEl.appendChild(child);
return divEl;
}, div);
}

Работает отлично. Однако эти две функции очень похожи, разница лишь в родительском элементе, который мы создали.

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

function createListWrapperFunction(elementType) {
// Straight away, we return a function.
return function wrap(children) {
// Inside our wrap function, we can ‘see’ the elementType parameter.
const parent = document.createElement(elementType);
return […children].reduce((parentEl, child) => {
parentEl.appendChild(child);
return parentEl;
}, parent);
}
}

Поначалу это может выглядеть немного сложно, так что давайте разделим код. Мы создали функцию, которая всего лишь возвращает другую функцию. Но эта возвращаемая функция помнит параметр elementType. И потом, когда мы вызываем возвращённую функцию, ей уже известно, какой элемент нужно создавать. Поэтому можно создать wrapWithUl и wrapWithDiv:

const wrapWithUl = createListWrapperFunction(‘ul’);
// Our wrapWithUl() function now ‘remembers’ that it creates a ul element.

const wrapWithDiv = createListWreapperFunction(‘div’);
// Our wrapWithDiv() function now ‘remembers’ that it creates a div element.

Эту хитрость, когда возвращённая функция «помнит» о чём-то, называют замыканием. Подробнее можно почитать о них здесь. Замыкания невероятно удобны, но пока что мы не будем о них думать.

Итак, мы разобрали:

  • Присвоение функции переменной.
  • Передачу функции в качестве параметра.
  • Возвращение функции из другой функции…

В общем, функции первого класса — штука приятная. Но при чём тут функции высшего порядка? Давайте рассмотрим определение.

Что такое функция высшего порядка?

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

Знакомо? В JavaScript это функции первого класса. То есть «функции высшего порядка» обладают точно такими же преимуществами. Иными словами, это просто вычурное название для простой идеи.

Примеры функций высшего порядка

Если начать искать, то начинаешь везде замечать функции высшего порядка. Самыми распространёнными являются функции, которые принимают другие функции в качестве параметров.

Функции, принимающие другие функции в качестве параметров

Когда вы передаёте callback, вы используете функцию высшего порядка. Во фронтенд-разработке они встречаются повсеместно. Одна из самых распространённых — метод .addEventListener(). Мы используем его, когда хотим выполнить действия в ответ на какие-то события. К примеру, я хочу сделать кнопку, выдающую предупреждение:

function showAlert() {
alert(‘Fallacies do not cease to be fallacies because they become fashions’);
}

document.body.innerHTML += `<button type=»button» class=»js-alertbtn»>
Show alert
</button>`;

const btn = document.querySelector(‘.js-alertbtn’);

btn.addEventListener(‘click’, showAlert);

Читайте также:  Что полезного можно сделать из досок

Здесь мы создали функцию, показывающую предупреждение, добавили на страницу кнопку и передали функцию showAlert() в качестве аргумента в btn.addEventListener().

Также мы встречаем функции высшего порядка, когда используем методы итерации массивов: например, .map(), .filter() и .reduce(). Как в функции elListMap():

function elListMap(transform, list) {
return […list].map(transform);
}

Также функции высшего порядка помогают работать с задержками и таймингом. Функции setTimeout() и setInterval() помогают управлять тем, когда исполняются функции. Например, если нужно через 30 секунд убрать класс highlight, можно это сделать так:

function removeHighlights() {
const highlightedElements = document.querySelectorAll(‘.highlighted’);
elListMap(el => el.classList.remove(‘highlighted’), highlightedElements);
}

setTimeout(removeHighlights, 30000);

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

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

Функции, возвращающие функции

Функции такого вида встречаются не столь часто, как предыдущие. Но они тоже полезны. Один из лучших примеров — функция maybe(). Я адаптировал вариант из книги JavaScript Allongé:

function maybe(fn)
return function _maybe(…args) {
// Note that the == is deliberate.
if ((args.length === 0) || args.some(a => (a == null)) {
return undefined;
}
return fn.apply(this, args);
}
}

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

// Apply a given function to every item in a NodeList and return an array.
function elListMap(transform, list) {
// list might be a NodeList, which doesn’t have .map(), so we convert
// it to an array.
return […list].map(transform);
}

Что будет, если случайно передать в elListMap() null или неопределённое значение? Мы получим TypeError и падение текущей операции, какой бы она ни была. Избежать этого можно с помощью функции maybe():

const safeElListMap = maybe(elListMap);
safeElListMap(x => x, null);
// ← undefined

Вместо падения функция вернёт undefined. А если бы мы передали это в другую функцию, защищённую maybe(), то в ответ снова получили бы undefined. С помощью maybe()можно защитить любое количество функций, это гораздо проще написания миллиарда выражений if.

Функции, возвращающие функции, также часто встречаются в мире React. Например, connect().

И что дальше?

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

Чтобы ответить на этот вопрос, давайте рассмотрим ещё один пример — встроенный метод массива .sort(). Да, у него есть недостатки. Он меняет массив вместо того, чтобы возвращать новый. Но давайте пока об этом забудем. Метод .sort() является функцией высшего порядка, он принимает другую функцию в качестве одного из параметров.

Как это работает? Если мы хотим отсортировать массив чисел, сначала нужно создать функцию сравнения:

function compareNumbers(a, b) {
if (a === b) return 0;
if (a > b) return 1;
/* else */ return -1;
}

Теперь отсортируем массив:

let nums = [7, 3, 1, 5, 8, 9, 6, 4, 2];
nums.sort(compareNumbers);
console.log(nums);
// 〕[1, 2, 3, 4, 5, 6, 7, 8, 9]

Можно сортировать и списки чисел. Но какая от этого польза? Насколько часто у нас есть список чисел, который нужно сортировать? Не часто. Обычно мне нужно сортировать массив объектов:

let typeaheadMatches = [
{
keyword: ‘bogey’,
weight: 0.25,
matchedChars: [‘bog’],
},
{
keyword: ‘bog’,
weight: 0.5,
matchedChars: [‘bog’],
},
{
keyword: ‘boggle’,
weight: 0.3,
matchedChars: [‘bog’],
},
{
keyword: ‘bogey’,
weight: 0.25,
matchedChars: [‘bog’],
},
{
keyword: ‘toboggan’,
weight: 0.15,
matchedChars: [‘bog’],
},
{
keyword: ‘bag’,
weight: 0.1,
matchedChars: [‘b’, ‘g’],
}
];

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

function compareTypeaheadResult(word1, word2) {
return -1 * compareNumbers(word1.weight, word2.weight);
}

typeaheadMatches.sort(compareTypeaheadResult);
console.log(typeaheadMatches);
// 〕[{keyword: «bog», weight: 0.5, matchedChars: [«bog»]}, … ]

Можно написать функцию сравнения для любого вида массивов. Метод .sort() нам помогает: «Если дадите мне функцию сравнения, я отсортирую любой массив. Не волнуйтесь о его содержимом. Если дадите функцию сортировки, я это отсортирую». Поэтому нам не нужно самостоятельно писать алгоритм сортировки, мы сосредоточимся на гораздо более простой задаче сравнения двух элементов.

Теперь представим, что мы не используем функции высшего порядка. Мы не можем передать функцию методу .sort(). Нам придётся писать новую функцию сортировки каждый раз, когда нужно отсортировать массив другого вида. Или придётся переизобретать то же самое с указателями функций или объектами. В любом случае получится очень неуклюже.

Однако у нас есть функции высшего порядка, которые позволяют отделить функцию сортировки от функции сравнения. Допустим, сообразительный разработчик браузера обновил .sort(), чтобы тот использовал более быстрый алгоритм. Тогда ваш код только выиграет, вне зависимости от того, что находится внутри сортируемых массивов. И эта схема верна для целого набора функций массивов высшего порядка.

Это приводит нас к такой идее. Метод .sort() абстрагирует задачу сортировки от содержимого массива. Это называется «разделение обязанностей» (separation of concerns). Функции высшего порядка позволяют создавать абстракции, которые без них были бы очень громоздки или вообще невозможны. А на создание абстракций приходится 80 % работы программных инженеров.

Когда мы рефакторим код, чтобы убрать повторы, мы создаём абстракции. Видим паттерн и заменяем его абстрактным представлением. В результате код становится более осмысленным и простым в понимании. По крайней мере, такова цель.

Функции высшего порядка — мощный инструмент создания абстракций. И с абстракциями связан целый раздел математики, теория категорий. Точнее, теория категорий посвящена поиску абстракций абстракций. Иными словами, речь идёт о поиске паттернов паттернов. И за последние 70 лет умные программисты позаимствовали оттуда немало идей, которые превратились в свойства языков и библиотеки. Если мы выучим эти паттерны паттернов, то иногда сможем заменять большие куски кода. Или упрощать сложные проблемы до элегантных комбинаций из простых строительных блоков. Эти блоки — функции высшего порядка. Поэтому они столь важны, они дают нам мощный инструмент для борьбы со сложностью нашего кода.

Дополнительные материалы про функции высшего порядка:

  • Функции высшего порядка: пятая глава из Eloquent JavaScript.
  • Функции высшего порядка: часть серии статей Composing Sofware.
  • Функции высшего порядка в JavaScript.

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

Если вы опытны в функциональном программировании, то могли заметить, что я использовал не чистые функции и некоторые… многословные имена функций. Это не потому, что не слышал про нечистые функции или общие принципы функционального программирования. И я не пишу такой код в production. Я старался подобрать практические примеры, которые будут понятны новичкам. Иногда приходилось идти на компромиссы. Если интересно, то я уже писал о функциональной чистоте и общих принципах функционального программирования.

Источник