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

Создание функций

Существует 3 способа создать функцию. Основное отличие в результате их работы - в том, что именованная функция видна везде, а анонимная - только после объявления:

Функции - объекты

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

Function f() { ... } f.test = 6 ... alert(f.test) // 6

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

Например,

Function func() { var funcObj = arguments.callee funcObj.test++ alert(funcObj.test) } func.test = 1 func() func()

В начале работы каждая функция создает внутри себя переменную arguments и присваивает arguments.callee ссылку на себя. Так что arguments.callee.test - свойство func.test , т.е статическая переменная test.

В примере нельзя было сделать присвоение:

Var test = arguments.callee.test test++

так как при этом операция ++ сработала бы на локальной переменной test , а не на свойстве test объекта функции.

Объект arguments также содержит все аргументы и может быть преобразован в массив (хотя им не является), об этом - ниже, в разделе про параметры.

Области видимости

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

Переменные можно объявлять в любом месте. Ключевое слово var задает переменную в текущей области видимости. Если его забыть, то переменная попадет в глобальный объект window . Возможны неожиданные пересечения с другими переменными окна, конфликты и глюки.

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

Заданная через var переменная видна везде в области видимости, даже до оператора var . Для примера сделаем функцию, которая будет менять переменную, var для которой находится ниже.

Например:

Function a() { z = 5 // поменяет z локально.. // .. т.к z объявлена через var var z } // тест delete z // очистим на всякий случай глобальную z a() alert(window.z) // => undefined, т.к z была изменена локально

Параметры функции

Функции можно запускать с любым числом параметров.

Если функции передано меньше параметров, чем есть в определении, то отсутствующие считаются undefined .

Следующая функция возвращает время time , необходимое на преодоление дистанции distance с равномерной скоростью speed .

При первом запуске функция работает с аргументами distance=10 , speed=undefined . Обычно такая ситуация, если она поддерживается функцией, предусматривает значение по умолчанию:

// если speed - ложное значение(undefined, 0, false...) - подставить 10 speed = speed || 10

Оператор || в яваскрипт возвращает не true/false , а само значение (первое, которое приводится к true).

Поэтому его используют для задания значений по умолчанию. В нашем вызове speed будет вычислено как undefined || 10 = 10 .

Поэтому результат будет 10/10 = 1 .

Второй запуск - стандартный.

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

Ну и в последнем случае аргументов вообще нет, поэтому distance = undefined , и имеем результат деления undefined/10 = NaN (Not-A-Number, произошла ошибка).

Работа с неопределенным числом параметров

Непосредственно перед входом в тело функции, автоматически создается объект arguments , который содержит

  • Аргументы вызова, начиная от нуля
  • Длину в свойстве length
  • Ссылку на саму функцию в свойстве callee
  • Например,

    Function func() { for(var i=0;i alert(3)

    Пример передачи функции по ссылке

    Функцию легко можно передавать в качестве аргумента другой функции.

    Например, map берет функцию func , применяет ее к каждому элементу массива arr и возвращает получившийся массив:

    Var map = function(func, arr) { var result = for(var i=0; i target) return null; else return find(start + 5, "(" + history + " + 5)") || find(start * 3, "(" + history + " * 3)"); } return find(1, "1"); } console.log(findSolution(24)); // → (((1 * 3) + 5) * 3)

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

    Внутренняя функция find занимается рекурсией. Она принимает два аргумента – текущее число и строку, которая содержит запись того, как мы пришли к этому номеру. И возвращает либо строчку, показывающую нашу последовательность шагов, либо null.

    Для этого функция выполняет одно из трёх действий. Если заданное число равно цели, то текущая история как раз и является способом её достижения, поэтому она и возвращается. Если заданное число больше цели, продолжать умножения и сложения смысла нет, потому что так оно будет только увеличиваться. А если мы ещё не достигли цели, функция пробует оба возможных пути, начинающихся с заданного числа. Она дважды вызывает себя, один раз с каждым из способов. Если первый вызов возвращает не null, он возвращается. В другом случае возвращается второй.

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

    Find(1, "1") find(6, "(1 + 5)") find(11, "((1 + 5) + 5)") find(16, "(((1 + 5) + 5) + 5)") too big find(33, "(((1 + 5) + 5) * 3)") too big find(18, "((1 + 5) * 3)") too big find(3, "(1 * 3)") find(8, "((1 * 3) + 5)") find(13, "(((1 * 3) + 5) + 5)") found!

    Отступ показывает глубину стека вызовов. В первый раз функция find вызывает сама себя дважды, чтобы проверить решения, начинающиеся с (1 + 5) и (1 * 3). Первый вызов ищет решение, начинающееся с (1 + 5), и при помощи рекурсии проверяет все решения, выдающие число, меньшее или равное требуемому. Не находит, и возвращает null. Тогда-то оператор || и переходит к вызову функции, который исследует вариант (1 * 3). Здесь нас ждёт удача, потому что в третьем рекурсивном вызове мы получаем 13. Этот вызов возвращает строку, и каждый из операторов || по пути передаёт эту строку выше, в результате возвращая решение.

    Выращиваем функции Существует два более-менее естественных способа ввода функций в программу.

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

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

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

    007 Коров 011 Куриц

    Очевидно, что нам понадобится функция с двумя аргументами. Начинаем кодить.
    // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens) { var cowString = String(cows); while (cowString.length < 3) cowString = "0" + cowString; console.log(cowString + " Коров"); var chickenString = String(chickens); while (chickenString.length < 3) chickenString = "0" + chickenString; console.log(chickenString + " Куриц"); } printFarmInventory(7, 11);

    Если мы добавим к строке.length, мы получим её длину. Получается, что циклы while добавляют нули спереди к числам, пока не получат строчку в 3 символа.

    Готово! Но только мы собрались отправить фермеру код (вместе с изрядным чеком, разумеется), он звонит и говорит нам, что у него в хозяйстве появились свиньи, и не могли бы мы добавить в программу вывод количества свиней?

    Можно, конечно. Но когда мы начинаем копировать и вставлять код из этих четырёх строчек, мы понимаем, что надо остановиться и подумать. Должен быть способ лучше. Пытаемся улучшить программу:

    // выводСДобавлениемНулейИМеткой function printZeroPaddedWithLabel(number, label) { var numberString = String(number); while (numberString.length < 3) numberString = "0" + numberString; console.log(numberString + " " + label); } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { printZeroPaddedWithLabel(cows, "Коров"); printZeroPaddedWithLabel(chickens, "Куриц"); printZeroPaddedWithLabel(pigs, "Свиней"); } printFarmInventory(7, 11, 3);

    Работает! Но название printZeroPaddedWithLabel немного странное. Оно объединяет три вещи – вывод, добавление нулей и метку – в одну функцию. Вместо того, чтобы вставлять в функцию весь повторяющийся фрагмент, давайте выделим одну концепцию:

    // добавитьНулей function zeroPad(number, width) { var string = String(number); while (string.length < width) string = "0" + string; return string; } // вывестиИнвентаризациюФермы function printFarmInventory(cows, chickens, pigs) { console.log(zeroPad(cows, 3) + " Коров"); console.log(zeroPad(chickens, 3) + " Куриц"); console.log(zeroPad(pigs, 3) + " Свиней"); } printFarmInventory(7, 16, 3);

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

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

    Хорошее правило – добавляйте только ту функциональность, которая вам точно пригодится. Иногда появляется искушение создавать фреймворки общего назначения для каждой небольшой потребности. Сопротивляйтесь ему. Вы никогда не закончите работу, а просто напишете кучу кода, который никто не будет использовать.

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

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

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

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

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

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

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

    УпражненияМинимум В предыдущей главе была упомянута функция Math.min, возвращающая самый маленький из аргументов. Теперь мы можем написать такую функцию сами. Напишите функцию min, принимающую два аргумента, и возвращающую минимальный из них.

    Console.log(min(0, 10)); // → 0 console.log(min(0, -10)); // → -10

    Рекурсия Мы видели, что оператор % (остаток от деления) может использоваться для определения того, чётное ли число (% 2). А вот ещё один способ определения:

    Ноль чётный.
    Единица нечётная.
    У любого числа N чётность такая же, как у N-2.

    Напишите рекурсивную функцию isEven согласно этим правилам. Она должна принимать число и возвращать булевское значение.

    Потестируйте её на 50 и 75. Попробуйте задать ей -1. Почему она ведёт себя таким образом? Можно ли её как-то исправить?

    Test it on 50 and 75. See how it behaves on -1. Why? Can you think of a way to fix this?

    Console.log(isEven(50)); // → true console.log(isEven(75)); // → false console.log(isEven(-1)); // → ??

    Считаем бобы.

    Символ номер N строки можно получить, добавив к ней.charAt(N) (“строчка”.charAt(5)) – схожим образом с получением длины строки при помощи.length. Возвращаемое значение будет строковым, состоящим из одного символа (к примеру, “к”). У первого символа строки позиция 0, что означает, что у последнего символа позиция будет string.length – 1. Другими словами, у строки из двух символов длина 2, а позиции её символов будут 0 и 1.

    Напишите функцию countBs, которая принимает строку в качестве аргумента, и возвращает количество символов “B”, содержащихся в строке.

    Затем напишите функцию countChar, которая работает примерно как countBs, только принимает второй параметр - символ, который мы будем искать в строке (вместо того, чтобы просто считать количество символов “B”). Для этого переделайте функцию countBs.

    Последнее обновление: 09.04.2018

    Функции представляют собой набор инструкций, выполняющих определенное действие или вычисляющих определенное значение.

    Синтаксис определения функции:

    Function имя_функции([параметр [, ...]]){ // Инструкции }

    Определение функции начинается с ключевого слова function , после которого следует имя функции. Наименование функции подчиняется тем же правилам, что и наименование переменной: оно может содержать только цифры, буквы, символы подчеркивания и доллара ($) и должно начинаться с буквы, символа подчеркивания или доллара.

    После имени функции в скобках идет перечисление параметров. Даже если параметров у функции нет, то просто идут пустые скобки. Затем в фигурных скобках идет тело функции, содержащее набор инструкций.

    Определим простейшую функцию:

    Function display(){ document.write("функция в JavaScript"); }

    Данная функция называется display() . Она не принимает никаких параметров и все, что она делает, это пишет на веб-страницу строку.

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

    function display(){ document.write("функция в JavaScript"); } display();

    Необязательно давать функциям определенное имя. Можно использовать анонимные функции:

    Var display = function(){ // определение функции document.write("функция в JavaScript"); } display();

    Фактически мы определяем переменную display и присваиваем ей ссылку на функцию. А затем по имени переменной функция вызывается.

    Также мы можем динамически присваивать функции для переменной:

    Function goodMorning(){ document.write("Доброе утро"); } function goodEvening(){ document.write("Добрый вечер"); } var message = goodMorning; message(); // Доброе утро message = goodEvening; message(); // Добрый вечер

    Параметры функции

    Рассмотрим передачу параметров:

    Function display(x){ // определение функции var z = x * x; document.write(x + " в квадрате равно " + z); } display(5); // вызов функции

    Функция display принимает один параметр - x. Поэтому при вызове функции мы можем передать для него значение, например, число 5, как в данном случае.

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

    Function sum(a, b, c){ let d = a + b + c; console.log(d); } sum(1, 2, 3); let nums = ; sum(...nums);

    Во втором случае в функцию передается числа из массива nums. Но чтобы передавался не просто массив, как одно значение, а именно числа из этого массива, применяется spread-оператор (многоточие...).

    Необязательные параметры

    Функция может принимать множество параметров, но при этом часть или все параметры могут быть необязательными. Если для параметров не передается значение, то по умолчанию они имеют значение "undefined".

    Function display(x, y){ if(y === undefined) y = 5; if(x === undefined) x = 8; let z = x * y; console.log(z); } display(); // 40 display(6); // 30 display(6, 4) // 24

    Здесь функция display принимает два параметра. При вызове функции мы можем проверить их значения. При этом, вызывая функцию, необязательно передавать для этих параметров значения. Для проверки наличия значения параметров используется сравнение со значением undefined .

    Есть и другой способ определения значения для параметров по умолчанию:

    Function display(x = 5, y = 10){ let z = x * y; console.log(z); } display(); // 50 display(6); // 60 display(6, 4) // 24

    Если параметрам x и y не передаются значения, то они получаются в качестве значений числа 5 и 10 соответствено. Такой способ более лаконичен и интуитивен, чем сравнение с undefined.

    При этом значение параметра по умолчанию может быть производным, представлять выражение:

    Function display(x = 5, y = 10 + x){ let z = x * y; console.log(z); } display(); // 75 display(6); // 96 display(6, 4) // 24

    В данном случае значение параметра y зависит от значения x.

    При необходимости мы можем получить все переданные параметры через глобально доступный массив arguments :

    Function display(){ var z = 1; for(var i=0; i