JavaScript: функция в функции. Язык программирования JS. Функциональное программирование

Любому программисту хорошо известно, что такое функции и зачем они нужны. Однако функциям в языке Javascript присущи некоторые особенности. Если вы давно программируете именно на этом языке, то наверняка знаете, что существуют разные . Если же вы пришли из другого языка, то при прочтении некоторых статей скорее всего видели вот такое, странное на первый взгляд, объявление функции:

Var add = function(arg1, arg2) { var sum = arg1 + arg2; return sum; } var result = add(5, 2); //result теперь 7

То есть функция, во-первых, не имеет имени. Во-вторых, она присваивается переменной, но не просто присваивается, а тут же идет ее тело. Лично у меня, до этого писавшего на таких языках как VB, C++, подобное объявление вызывало недоумение и непонимание, как это устроено и зачем вообще так писать.

Я привык к "классическому" объявлению функции и ее вызову, вот так:

Function add(arg1, arg2) { var sum = arg1 + arg2; return sum; } var result = add(5, 3); //result теперь 8

И вот здесь как раз мы подходим к особенностям функций в Javascript. Для простоты понимания представьте себе, что функция в JS - это обычное значение, как, например, число или строка. Вы же можете записать в переменную result число 5? Или что-то более сложное, вроде массива, а потом вывести это на экран? Можете. Так вот, если представить, что функция - это обычное значение, пусть и весьма сложной структуры, то первый способ объявления уже не кажется чем-то невероятным.

Следующий интересный факт является логическим продолжением первого. После того как мы помещаем данные в переменную, мы можем посредством имени этой переменной передать данные в другую переменную:

Var a = 5; var b = a; alert(b); //выведет 5

Обычное дело. А теперь взгляните вот на этот код:

Var add = function(arg1, arg2) { var sum = arg1 + arg2; return sum; } var calcSum = add; alert(calcSum(5, 6)); //выведет 11

Начинаете уже догадываться? Поскольку функция - как переменная, мы можем "размножать" ее посредством обычного присваивания в другие переменные, превращая их так же в функции. Теперь calcSum тоже умеет складывать два числа. Однако код

Var calcSum = add(1, 1); //calcSum теперь равно 2, это не функция, а переменная с числом alert(calcSum(5, 6)); //ошибка

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

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

Var calcFact = function fact(val) { if (val == 1) ? val: val * fact(val — 1); //вычисление факториала с помощью рекурсии } alert(calcFact(4)); //выведет 24

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

Возможно, вы задаетесь вопросом "Хм, интересная возможность! Но какое преимущество у этого способа? Есть ли ситуации, когда без этого не обойтись или это хотя бы более удобно, чем обычное объявление?". Не возьмусь утверждать, что есть ситуации, где без такого подхода обойтись нельзя, но пример, где он уменьшает количество кода, привести могу. Допустим, вам нужно поприветствовать человека в зависимости от времени суток:

Var date = new Date(); var hello = (date.getHours() < 12) ? function() {alert("Доброе утро!")} : (date.getHours() < 18) ? function() {alert("Добрый день!")} : function() {alert("Добрый вечер!")}; hello();

Как видите, функции крайне простые, с одной-единственной командой alert.

Если бы мы решили пойти "классическим путем", то пришлось бы писать три отдельные функции, а потом вызывать их в условии проверки времени:

Function goodMorning() { alert("Доброе утро!"); } function goodAfternoon() { alert("Добрый день!"); } function goodEvning() { alert("Добрый вечер!"); } var date = new Date(); (date.getHours() < 12) ? goodMorning() : (date.getHours() < 18) ? goodAfternoon() : goodEvning();

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

Во-вторых, если мы используем "классический" метод, то лишаемся возможности присвоить функцию переменной. То есть написать

Function add(a, b) { return a + b; } var calcSum = add; calcSum(5, 5);

Уже нельзя. Поэтому в нашем примере, если все-таки понадобится приветствовать гостя не единожды, нам придется каждый раз дублировать вот этот фрагмент:

(date.getHours() < 12) ? goodMorning() : (date.getHours() < 18) ? goodAfternoon() : goodEvning();

А в первом случае достаточно будет написать всего лишь hello(); и результат будет тот же.

Я рассказал вам про интересную особенность функций JS и привел примеры. Таким образом вы увидели, что способы вызова функций в Javascript не ограничиваются одним-единственным видом. Даже если вы и не сможете сразу найти применение этим возможностям в ваших проектах, то хотя бы будете знать, что такие возможности есть. И когда подвернется действительно удачный случай, то сможете сократить объем кода и избежать лишней путаницы и ошибок.!

В этой главе:

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

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

Определение и вызов функций

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

Function ИмяФункции (аргументы) { блок выражений }

Таким образом, функция состоит из следующих частей, предваряемых ключевым словом function:

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

В простейшем случае аргументы могут отсутствовать, а блок операций моет быть представлен единственным оператором:

Function MyFirstFunc () { var MyMessage="Это – моя функция!"; alert(MyMessage); }

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

ВНИМАНИЕ
Важное замечание следует сделать по поводу переменных, объявляемых в теле функций. Такие переменные видны программе только внутри той функции, в которой они определены. Так, в примере с MyFirstFunc, доступ к переменной MyMessage возможен только внутри этой функции, но не вне нее.

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

Function remainder_free(j) { var i=0; while (i

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

ИмяФункции(параметры)

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

Remainder_free(100);

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

Remainder_free; // так функции не вызывают вообще remainder_free(); // недостаточно параметров remainder_free(100,0); // лишний параметр

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

Function cube(value) { return value * value * value; }

Таким образом, чтобы в любом месте программы вычислить значение числа в 3-й степени, достаточно написать такое выражение:

Var x = cube(3);

В результате выполнения этого выражения переменной x будет присвоено значение 27. Примеры вызова функций вы можете посмотреть в JavaScript-калькуляторе (файл calc.html).

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

Function remainder_free(j) { var i=0, ic=0; while (i

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

Вложение функций и рекурсия

Как и в некоторых других языках программирования (например, как в Паскале), функции в JavaScript могут быть вложенными. Суть использования вложенных функций состоит в том, что такая локальная функция не видна из других частей программы, что может быть удобными при разработке достаточно больших проектов, например, чтобы исключить переопределения имен переменных. Допустим, если бы рассмотренная нами функция cube была бы ненужной по ходу всего сценария, за исключением одного частного случая (скажем, функции, складывающей кубы двух чисел), то можно было бы сделать ее вложенной:

Function AddCubes(x,y) { function cube(value) { return value * value * value; }; return cube(x) + cube(y); }

В данном случае определена функция AddCubes, внутри которой определена еще одна функция – cube. При этом следует учитывать, что для вложенной функции видны переменные, используемые во внешней («наружной») функции, но не наоборот. В то же время сама вложенная функция доступна только для выражений из внешней функции: в данном случае функцией cube можно будет воспользоваться только из функции AddCubes.

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

Function recfunc(x) { x--; if (x>5) recfunc(x); alert(x); }

Здесь мы объявили функцию recfunc, принимающую один аргумент, и вызывающую саму себя до тех пор, пока значение этого аргумента больше 5. Хотя на первый взгляд может показаться, что такое поведение функции похоже на обычный цикл, на самом деле все работает несколько по-иному: если вы вызовите ее со значением 8, то она выдаст вам 3 сообщения в следующей последовательности: 5, 6, 7. Иначе говоря, функция вызывала саму себя до тех пор, пока значение x было больше 5, и собственно вывод сообщений начала 3-я по уровню получившейся вложенности функция, которая и вывела первое сообщение (в данном случае им стало декрементированное 6, т.е. 5).

Массивы аргументов

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

Arguments[i]; ИмяФункции.arguments[i];

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

Arguments.length;

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

Function ConcatSeparated(separator) { result=""; // инициализируем возвращаемое значение for (var i=1; i

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

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

Function calcAvg() { var summ=0; // инициализируем переменные for (var i=0; i

Здесь хотелось бы обратить внимание на то, что обход массива начинается с нулевого элемента. Это вполне естественно, поскольку нумерация элементов массивов начинается с 0. Если же у вас возник вопрос, почему в предыдущем примере отсчет начинался с 1, то обратите внимание на то, что первым аргументом там был separator, объявленный явно, и который ни с чем склеивать не требовалось.

Другой момент – это использование сокращенной формы записи для подсчета суммы. Конечно, можно было бы написать это выражение в более привычном виде:

Summ = summ + arguments[i];

Но, поскольку уж в JavaScript предусмотрены сокращенные формы записи, то почему бы их и не использовать? Ну и, наконец, после оператора return записана не переменная, а выражение, что так же вполне допустимо с точки зрения синтаксиса языка.

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

Var avg = calcAvg(1,3,5); // переменной avg будет присвоено значение 3 var avg = calcAvg(2); // переменной avg будет присвоено значение 2 var avg = calcAvg(0,4,8,14); // переменной avg будет присвоено значение 6,5

Заметьте, что во всех случаях используется некоторая переменная (avg), которой присваивается значение, возвращаемое функцией. Если бы такой переменной не было указано, то результат вычислений попросту пропал бы. Иначе говоря, если функция возвращает какое-либо значение, то оно должно использоваться – не обязательно путем присвоения переменной, но и любым другим способом:

Alert(calcAvg(10,100));

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

Предопределенные функции

В JavaScript имеется ряд функций, являющихся частью самого языка, и не связанных с какими-либо конкретными объектами (вернее, они являются функциями некоего глобального объекта global, на который, тем не менее, никогда не требуется ссылаться). Такие функции называются предопределенными.

К предопределенным функциям JavaScript 1.3 относятся: eval, isFinite, isNaN, parseInt, parseFloat, Number, String, escape и unescape. В JavaScript 1.5 вместо функций escape и unescape используются 4 новых функции: encodeURI, encodeURIComponent, decodeURI, и decodeURIComponent. Краткое описание всех этих функций приведено в таблице 4.9.

Таблица 4.9. Предопределенные функции языка JavaScript
Функция Синтаксис Описание
eval eval(выражение) Обрабатывает строку как код JavaScript
isFinite isFinite(значение) Проверяет, что значение является конечным числом
isNaN isNaN(значение) Проверяет, что значение не является числом
parseInt parseInt(строка, система) Преобразует строку в целое
parseFloat parseFloat(строка) Преобразует строку в вещественное число
Number Number(объект) Преобразует объект в число
String String(объект) Преобразует объект в строку
escape escape(строка ASCII) Преобразует строку в последовательность символов
unescape unescape(строка) Преобразует последовательность escape-символов в строку
encodeURI encodeURI(URL в ASCII) Кодирует URI полностью путем замены символов на их коды UTF-8
encodeURIComponent encodeURIComponent(URL в ASCII) Кодирует URI по составным частям путем замены символов на их коды UTF-8
decodeURI decodeURI(URL в UTF-8) Раскодирует значение, созданное при помощи encodeURI
decodeURIComponent decodeURIComponent(URL в UTF-8) Раскодирует значение, созданное при помощи encodeURIComponent

Рассмотрим некоторые функции подробнее. Начнем с функции eval, которая передает интерпретатору строку с кодом JavaScript без ссылки на какой-либо конкретный объект. Способ использования у этой функции следующий:

Eval(выражение);

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

Функция isFinite проверяет, является ли переданное ей значение конечным числом. Если в качестве значения, передаваемого этой функции, указать -3, то результатом будет истина. А если указать Infinity (специальное ключевое слово, обозначающее бесконечность), результатом будет ложь, как, впрочем, и в том случае, если вместо числа указать строку.

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

IsNaN(NaN);

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

Var a = parseFloat("31416e-4");

В этом случае переменной a будет присвоено значение 3,1416. В том случае, если преобразовать строку в число не представляется возможным, функция parseFloat возвращает значение NaN.

Кроме функции parseFloat, для преобразования строк в число имеется функция parseInt. В отличие от parseFloat, она может принимать как один, так и два аргумента:

ParseInt("25"); parseInt("25", 10);

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

Var x = parseInt("15"); /* если не оговорено иначе, подразумевается, что система счисления – десятеричная */ var x = parseInt("15", 10); /* здесь мы явно указываем десятеричную систему счисления */ var x = parseInt(15.99, 10); /* в данном случае исходным значением служит не строка, а число с плавающей точкой */ var x = parseInt("F", 16); // шестнадцатеричное F равно десятеричному 15 var x = parseInt("FXX123", 16); /* если какой-либо знак распознать не удается, следующие за ним значения отбрасываются */ var x = parseInt("17", 8); // Восьмеричная система тоже иногда используется var x = parseInt("1111", 2); // Двоичная система – тоже не редкость

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

ParseInt("8",8); // В восьмеричной системе счисления нет цифры 8!

Для преобразования строк и чисел в JavaScript имеются еще 2 функции – String и Number. Они используются для преобразования объектов в строку и число соответственно. Например, если нам надо что-либо преобразовать в строку, например, объект document, то можно написать так:

Var doc = String(document);

В данном случае в качестве значения переменной doc мы получим строку, описывающую объект как «». Примеры работы этих и других предопределенных функций можно найти в файле predef.html.

И, наконец, осталось рассмотреть функции кодирования. На самом деле, в клиентских сценариях они практически не используются, и предназначены для работы на стороне сервера (для случая, если web-сервером является Netscape Enterprise). Так что мы не будем на них останавливаться, и отметим лишь, что, поскольку функции escape и unescape не работают с национальными символами, то, начиная с JavaScript 1.5, они объявлены устаревшими, а вместо них следует использовать encodeURI, decodeURI, encodeURIComponent и decodeURIComponent.

2011-07-28 // Есть вопросы, предложения, замечания? Вы можете

Встроенная функция описывает какие-либо действия, которые она может совершить при её вызове. Описание действий встроенных функций скрыто от программиста. Примером таких действий может служить вычисление математического выражения и возвращение результата или манипуляция с содержимым html-документа. Функция может принимать параметры или аргументы, с которыми она производит какие-то действия. Аргументами могут служить как , так и .

Вызов функции производится следующим образом:

var a = functionName(par1, par2, …, parN);

В js существует множество встроенных функций для подсчёта математических выражений. Например, функция Math.sin, возвращает синус угла (угол задаётся в радианах), функция Math.sqrt вычисляет корень квадратный из числа, переданного ей в качестве параметра и т.д.

Например, вычислим корень квадратный из 256.

var b = Math.sqrt(256);

После выполнения кода одного из двух вышепредставленных примеров в переменной b будет содержаться значение 16.

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

Функция alert

Эта функция принимает в качестве параметра текстовое представление значение, которое она выведет на экран в унылом сером окошке с кнопкой «Ок». Пока пользователь не нажмёт эту кнопку, выполнения скрипта не продолжится.

Функция document.write

Эта функция пишет в html-документ текстовое представление значения переданного ей в качестве параметра. Никогда не используйте этот метод при написании скриптов, для реальных проектов, если, конечно, точно не знаете, что делаете. Если вызывать эту функцию после загрузки страницы, то, скорее всего, вы увидите пустую страницу с текстом, который вывел последний вызов этой функции.

Объект Math

В этом объекте содержатся функции для вычисления математических выражений и некоторые константы. Об объектах мы поговорим попозже. Для того, чтобы использовать объект Math вам надо лишь запомнить, что обращение к его свойствам (функциям и константам) необходимо написать Math.имяФункцииИлиКонстанты.

Свойства, которые содержит объект Math (слово «Math» опущено):

1. Константы

E 2.718281828459045
LN10 2.302585092994046 (логарифм натуральный 10)
LN2 0.6931471805599453 (логарифм натуральный 2)
LOG10E 0.4342944819032518 (логарифм десятичный e)
LOG2E 1.4426950408889634 (логарифм по основанию 2 числа e)
PI 3.141592653589793
SQRT1_2 0.7071067811865476 (корень квадратный из 0.5)
SQRT2 1.4142135623730951 (корень квадратный из двух)

2. Тригонометрические функции

sin – синус
cos – косинус
tan – тангенс

Угол, который в качестве аргумента принимают эти функции задаётся в радианах, а не в градусах. Для того, чтобы перевести значение угла из градусов в радианы необходимо умножить его на Math.PI и разделить на 180. И наоборот для того, чтобы перевести значение угла из радианов в градусы необходимо умножить его на 180 и разделить на Math.PI.

Таким образом, 60 градусов это π / 3 радиан, 30 градусов это π / 6 радиан и 45 градусов π / 4 радиан.

3. Обратные тригонометрические функции

acos – арккосинус от числа т.е. такой угол (в радианах), косинус которого равен аргументу
asin – арксинус от числа т.е. такой угол (в радианах), синус которого равен аргументу
atan – арктангенс от числа т.е. такой угол (в радианах), тангенс которого равен аргументу
atan2 – арктангенс от частного двух аргументов

3. Другие функции

abs – модуль числа
floor – целая часть числа, “пол“ или округление в меньшую сторону. Обратите внимание, что, например Math.floor(-0.9) и Math.floor(-0.1) это -1, а не ноль.
ceil – округление в большую сторону или «потолок»
exp – возвращает значение выражения e x , где x – это аргумент функции
log – возвращает натуральный логарифм числа
pow – принимает два аргумента и возвращает степень основание которой это первый аргумент, а показатель – второй.
max – принимает произвольное количество параметров и возвращает максимальный из них
min – принимает произвольное количество параметров и возвращает минимальный из них
random – возвращает случайное значение от 0 до 1
round – округляет число до единиц
sqrt – вычисляет корень квадратный из числа

Многие другие встроенные функции мы рассмотрим по ходу следующих уроков, а на сегодня всё.

Статья в разработке!

Статья, в которой рассмотрим, что такое функция, а также традиционный (классический) вариант работы с ней. Кроме этого, разберем, что такое аргументы (параметры) функции и оператор return.

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

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

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

Как организовать выполнение некоторой задачи в JavaScript с использованием функций? Чтобы это выполнить обычно поступают так:

  • разбивают задачу на составные части (подзадачи);
  • подзадачи оформляют через функции;
  • разрабатывают основной код с использованием вызова созданных функций.

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

Объявление и вызов функции

Операции с функцией в JavaScript можно разделить на 2 шага:

  • объявление (создание) функции.
  • вызов (выполнение) этой функции.

Объявление функции. Создание функции в JavaScript начинается с написания ключевого слова function , далее указывается имя функции, затем в круглых скобка х при необходимости перечисляются параметры , после этого указываются инструкции , которые заключаются в фигурные скобки .

// объявление функции someName function someName() { alert("Вы вызвали функцию someName!"); } JavaScript - Синтаксис объявления функции

Функции такого вида в JavaScript называются function declaration statement . Кроме этого вида в JavaScript ещё различают функции function definition expression и arrow function expression .

Составление имени функции выполняется по тем же правилам, что и имя переменной. Т.е. оно может содержать буквы, цифры (0-9), знаки «$» и «_». В качестве букв рекомендуется использовать только буквы английского алфавита (a-z, A-Z). Имя функции, также как и имя переменной не может начинаться с цифры.

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

Набор инструкций , заключенный в фигурные скобки - это код функции , который будет выполнен при её вызове .

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

// вызов функции, приведённой в предыдущем примере someName(); JavaScript - Синтаксис вызова функции

Является ли функция в JavaScript объектом?

Функции в JavaScript являются объектами. В JavaScript вообще всё является объектами, кроме шести примитивных типов данных. А если функция является объектом, то ссылку на неё можно сохранить в переменную.

// объявление функции someName function someName() { alert("Вы вызвали функцию someName!"); } var reference = someName;

После этого вызвать функцию можно будет так:

Reference();

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

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

// вызов функции sayWelcome с передачей ей двух аргументов sayWelcome("Иван", "Иванов"); // ещё один вызов функции sayWelcome с двумя аргументами sayWelcome("Петр", "Петров");

Параметры функции – это один из способов в JavaScript, посредством которого можно обратиться к аргументам внутри функции. Описываются параметры функции на этапе её объявления в круглых скобках.

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

// объявление функции sayWelcome, которая имеет два параметра function sayWelcome (userFirstName, userLastName) { // инструкция, выводящая в консоль значения параметров «userFirstName» и «userLastName» console.log("Добро пожаловать, " + userLastName + " " + userFirstName); }

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

Например , вызовем функцию из примера, приведённого выше, без указания одного и двух параметров:

// вызов функции sayWelcome и передача ей одного аргумента sayWelcome("Петр"); // Добро пожаловать, undefined Петр // вызов функции sayWelcome без передачи ей аргументов sayWelcome(); // Добро пожаловать, undefined undefined

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

// объявление функции function outputParam(param1, param2, param3) { console.log(param1 + "; " + param2 + "; " + param3); } // вызовы функции outputParam с передачей ей разного количества параметров outputParam("Дождь","Снег","Туман"); // Дождь; Снег; Туман outputParam(17); // 17; undefined; undefined outputParam(24,33); // 24; 33; undefined outputParam(); // undefined; undefined; undefined

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

// объявление функции sum function sum(num1, num2) { /* num1 или arguments – получить значение 1 аргумента num2 или arguments – получить значение 2 аргумента */ var sum1 = num1 + num2, sum2 = arguments + arguments; return "Сумма, полученная 1 способом равна " + sum1 + "; сумма, полученная 2 способом равна " + sum2; } /* выведем результат функции sum в консоль 7 - первый аргумент (к нему можно обратиться как по имени num1, так и с помощью arguments) 4 - второй аргумент (к нему можно обратиться как по имени num2, так и с помощью arguments) */ console.log(sum(7,4));

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

Кроме получения аргументов, объект arguments позволяет также узнать их количество. Выполняется это с помощью свойства length.

Перебрать аргументы , переданные функции, можно, например, с помощью цикла for или for...of .

// объявление функции sum function sum() { var i = 0; console.log("Вывод всех аргументов с помощью цикла for"); for (i; i < arguments.length; i++) { console.log(i + 1 + " аргумент равен " + arguments[i]); } console.log("Вывод всех аргументов с помощью цикла for...of"); for (arg of arguments) { console.log(arg); } } // вызов функции sum sum(7, 4, 3, 1);

Функция, выводящая в консоль все переданные ей аргументы и их количество:

// объявление функции function myFunction () { var i; console.log("Количество переданных параметров = " + arguments.length); // переберём все параметры с помощью цикла for for (i = 0; i < arguments.length; i++) { console.log(i + " параметр = " + arguments[i]); } } // вызовы функции myFunction myFunction(3, 7, 27, "JavaScript"); myFunction(); myFunction("Яблоки", "Груши", "Апельсины");

Функция, выполняющая сложение все переданных ей аргументов (их количество заранее неизвестно):

// объявление функции var myCalc = function() { // переберём все параметры с помощью цикла for var i, sum = 0; for (i = 0; i lt; arguments.length; i++) { sum += arguments[i]; } // возвратим в качестве результата сумму return sum; } //вызов функции (вывод в консоль) console.log(myCalc(4, 20, 17, -6));

В результате, посредством объекта arguments можно реализовать в теле функции:

  • проверку количества переданных аргументов;
  • обработку какого угодного количества параметров.

Кроме самой функции, доступ к аргументам, которые передаются ей на этапе вызова, имеют также другие функции, находящиеся в ней.

Function mainF(p1, p2) { function childF() { console.log("p1 = " + p1 + "; p2 = " + p2); } childF(); } mainF(3, 5); // p1 = 3; p2 = 5 mainF(4, 7); // p1 = 4; p2 = 7

Значение параметров по умолчанию

Начиная с версии ECMAScript 2015 (6) параметру функции можно задать значение, которое он будет иметь по умолчанию.

Например , установим параметру «color» значение по умолчанию, равное «#009688»:

Function setBGColor(color = "#009688") { document.body.style.backgroundColor = color; } setBGColor(); // цвет фона будет равен #009688 setBGColor("red"); // цвет фона будет равен red

До ECMAScript 2015 задать параметру значение по умолчанию можно, например, было так:

Function setBGColor(color) { color = color !== undefined ? color: "#009688"; // устанавливаем color значение по умолчанию, равное "#009688" document.body.style.backgroundColor = color; }

Оставшиеся параметры (rest parameters)

Если при вызове функции ей передать аргументов больше, чем у неё есть параметров, то получить оставшиеся можно с помощью, так называемых оставшихся параметров (rest patameters) . Данная возможность в языке появилась, начиная с ECMAScript 2015.

// ...nums - оставшиеся параметры, к которым можно обратиться в данном случае по имени nums function doMath(mathAction, ...nums) { var result = 0; nums.forEach(function(value) { switch (mathAction) { case "sum": result += value; break; case "sumCube": result += value**3; break; case "sumSquare": result += value**2; break; deafult: result = 0; } }) return result; } console.log(doMath("sum", 3, 4, 21, -4)); // 24 (3 + 4 + 21 + (-4)) console.log(doMath("sumSquare", 1, 4)); // 17 (1^2 + 4^2) console.log(doMath("sumCube", 3, 2, 4)); // 99 (3^3 + 2^3 + 4^3)

Оператор return

Оператор return предназначен для возвращения значения или результата вычисления выражения текущей функции. Значение или выражение должно быть отделено от return посредством пробела. Кроме этого, оператор return прекращает выполнение функции, т.е. все инструкции, идущие после него, исполнены не будут.

Функция в JavaScript всегда возвращает результат в вне зависимости от того, используется ли оператор return или нет.

// функция, возвращающая результат function sayWelcome (userFirstName, userLastName) { if ((!userFirstName) || (!userLastName)) return "Добро пожаловать, анонимный пользователь"; else return "Добро пожаловать, " + userLastName + " " + userFirstName; } // объявление переменной person var person; // присвоить переменной person результат функции sayWelcome person = sayWelcome("Иван","Иванов"); // вывести значение переменной в консоль console.log(person); //Инструкция, которая выведит в консоль результат работы функции sayWelcome console.log(sayWelcome("Петр","Петров")); //Инструкция, которая выведит в консоль результат работы функции sayWelcome console.log(sayWelcome("Сидоров")); JavaScript - Функция с проверкой параметров

Функция в JavaScript в результате своего выполнения всегда возвращает результат, даже если он явно не определён с помощью оператора return. Этот результат значение undefined.

// 1. функция, не возвращающая никакого результата function sayWelcome (userFirstName, userLastName) { console.log("Добро пожаловать, " + userLastName + " " + userFirstName); } // попробуем получить результат у функции, которая ничего не возвращает console.log(sayWelcome ("Иван", "Иванов")); // 2. функция, содержащая оператор return без значения function sayDay (day) { day = "Сегодня, " + day; return; //эта инструкция не выполнится, т.к. она идёт после оператора return console.log(day); } // попробуем получить результат у функции, которая содержит оператор return без значения console.log(sayDay("21 февраля 2016г.")); JavaScript - Получить значение у функции, которая ничего не возвращает

Такой же результат будет получен, если для оператора return не указать возвращаемое значение.

Перегрузка функций в JavaScript

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

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

Подобную функциональность можно реализовать в JavaScript используя следующие действия:

  • Для того чтобы проверить передан аргумент или нет, используйте условие с проверкой его значения на undefined .
  • Для проверки количества переданных аргументов функции используйте свойство объекта arguments length .
  • Чтобы узнать тип переданного значения аргумента используйте операторы typeof или instanceof .
  • Для работы с переменным числом аргументов, используйте объект arguments .
  • Начиная с версии ECMAScript6, Вы можете указывать значения по умолчанию для аргументов.

Например, создадим функцию, при вызове которой можно указывать один или два аргумента:

//объявление функции, которая изменяет цвет заднего фона элементов function setBgColor(bgColor,elements) { //если параметр elements при вызове не указан if (elements=== undefined) { //то приравнять его значение "div" elements = "div"; } //получить все элементы elements = $(elements); //перебрать все элементы и установить им указанный цвет заднего фона elements.each(function(){ $(this).css("background-color",bgColor); }); } /*Вызвать функцию setBgColor, указав один параметр. Т.к. 2 параметр не указан, то данная фукция изменит цвет заднего фона у всех элементов div.*/ setBgColor("green"); /*Вызвать функцию setBgColor, указав 2 параметра. Т.к. 2 параметр задан, то данная функция изменит цвет заднего фона только элементов button.*/ setBgColor("#ff0000","button");

Произведём некоторые изменения в вышепредставленном коде. А именно, укажем для второго параметра значение по умолчанию:

//объявление функции, которая изменяет цвет заднего фона элементов //параметр elements имеет значение "div" по умолчанию function setBgColor(bgColor,elements = "div") { //получить все элементы elements = $(elements); //перебрать все элементы и установить им указанный цвет заднего фона elements.each(function(){ $(this).css("background-color",bgColor); }); } //вызвать функцию setBgColor, указав один параметр setBgColor("green"); //вызвать функцию setBgColor, указав 2 параметра setBgColor("#ff0000","button");

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

// описание функции function countCal(sex, height) { // параметры: sex (пол) и height (рост) var result; if ((sex === 0) || (sex === "man")) { result = (height - 100) * 20; } else if ((sex === 1) || (sex === "woman")) { result = (height - 105) * 19; } if (result) { // arguments - уровень активности if (arguments) { result *= arguments; } console.log("Количество ккал для нормальной жизнедеятельности: " + result); } else { console.log("Неверно указаны параметры"); } } /* вызов функции и передаче ей 2 аргументов (1 - "man", к нему можно обратиться с помощью имени sex и arguments; 2 - значение 185, к нему можно обратиться с помощью имени sex и arguments) */ countCal("man", 185); /* вызов функции и передаче ей 3 параметров, хотя в описании функции присутствуют только 2 (получить значение 3 параметра в данном случае можно только как arguments) */ countCal(0, 185, 2);

Рекурсия

Рекурсия – это вызов внутри тела некоторой функции самой себя.

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

Function fact(n) { if (n === 1) { return 1; } return fact(n-1) * n; } console.log(fact(5)); // 120

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

Что такое встроенные (стандартные) функции?

В JavaScript имеется огромный набор встроенных (стандартных) функций. Данные функции уже описаны в самом движке браузера. Практически все они являются методами того или иного объекта.

Например, для того чтобы вызвать встроенную функцию (метод) alert, её не надо предварительно объявлять. Она уже описана в браузере. Вызов метода alert осуществляется посредством указания имени, круглых скобок и аргумента внутри них. Данный метод предназначен для вывода сообщения на экран в форме диалогового окна. Текстовое сообщение берётся из значения параметра данной функции.

// вызов функции alert alert("Некоторый текст"); JavaScript - Вызов функции alert

Функции - ключевая концепция в JavaScript. Важнейшей особенностью языка является первоклассная поддержка функций ​ (functions as first-class citizen) . Любая функция это объект, и следовательно ею можно манипулировать как объектом, в частности:

  • передавать как аргумент и возвращать в качестве результата при вызове других функций (функций высшего порядка);
  • создавать анонимно и присваивать в качестве значений переменных или свойств объектов.

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

Функция в JavaScript специальный тип объектов, позволяющий формализовать средствами языка определённую логику поведения и обработки данных.

Для понимания работы функций необходимо (и достаточно?) иметь представление о следующих моментах:

Объявление функций

Функции вида "function declaration statement"

Объявление функции (function definition , или function declaration , или function statement ) состоит из ключевого слова function и следующих частей:

  • Имя функции.
  • Список параметров (принимаемых функцией) заключенных в круглые скобки () и разделенных запятыми.
  • Инструкции, которые будут выполненны после вызова функции, заключают в фигурные скобки { } .

Например, следующий код объявляет простую функцию с именим square:

Function square(number) { return number * number; }

Функция square принимает один параметр, названный number. Состоит из одной инструкции, которая означает вернуть параметр этой функции (это number) умноженный на самого себя. Инструкция return указывает на значение, которые будет возвращено функцией.

Return number * number;

Примитивные параметры (например, число) передаются функции значением; значение передаётся в функцию, но если функция меняет значение параметра, это изменение не отразится глобально или после вызова функции.

Если Вы передадите объект как параметр (не примитив, например, или определяемые пользователем объкты), и функция изменит свойство переданного в неё объекта, это изменение будет видно и вне функции, как показано в следующим примере:

Function myFunc(theObject) { theObject.make = "Toyota"; } var mycar = {make: "Honda", model: "Accord", year: 1998}; var x, y; x = mycar.make; // x получает значение "Honda" myFunc(mycar); y = mycar.make; // y получает значение "Toyota" // (свойство было изменено функцией)

Функции вида "function definition expression"

Функция вида "function declaration statement" по синтаксису является инструкцией (statement ), ещё функция может быть вида "function definition expression". Такая функция может быть анонимной (она не имеет имени). Например, функция square может быть вызвана так:

Var square = function(number) { return number * number; }; var x = square(4); // x получает значение 16

Однако, имя может быть и присвоено для вызова самой себя внутри самой функции и для отладчика (debugger ) для идентифицирования функции в стек-треках (stack traces ; "trace" - "след" / "отпечаток").

Var factorial = function fac(n) { return n < 2 ? 1: n * fac(n - 1); }; console.log(factorial(3));

Функции вида "function definition expression" удобны, когда функция передается аргументом другой функции. Следующий пример показывает функцию map , которая должна получить функцию первым аргументом и массив вторым.

Function map(f, a) { var result = , // Create a new Array i; for (i = 0; i != a.length; i++) result[i] = f(a[i]); return result; }

В следующим коде наша функция принимает функцию, которая является function definition expression, и выполняет его для каждого элемента принятого массива вторым аргументом.

Function map(f, a) { var result = ; // Create a new Array var i; // Declare variable for (i = 0; i != a.length; i++) result[i] = f(a[i]); return result; } var f = function(x) { return x * x * x; } var numbers = ; var cube = map(f,numbers); console.log(cube);

Функция возвращает: .

В JavaScript функция может быть объявлена с условием. Например, следующая функция будет присвоена переменной myFunc только, если num равно 0:

Var myFunc; if (num === 0) { myFunc = function(theObject) { theObject.make = "Toyota"; } }

В дополнение к объявлениям функций, описанных здесь, Вы также можете использовать конструктор Function для создания функций из строки во время выполнения (runtime ), подобно .

Метод - это функция, которая является свойством объекта. Узнать больше про объекты и методы можно по ссылке: Работа с объектами .

Вызовы функций

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

Square(5);

Эта инструкция вызывает функцию с аргументом 5. Функция вызывает свои инструкции и возвращает значение 25.

Функции могут быть в области видимости, когда они уже определены, но функции вида "function declaration statment" могут быть подняты (поднятие - hoisting ), также как в этом примере:

Console.log(square(5)); /* ... */ function square(n) { return n * n; }

Область видимости функции - функция, в котором она определена, или целая программа, если она объявлена по уровню выше.

Примечание: Это работает только тогда, когда объявлении функции использует вышеупомянутый синтаксис (т.е. function funcName(){}). Код ниже не будет работать. Имеется в виду то, что поднятие функции работает только с function declaration и не работает с function expression.

Console.log(square); // square поднят со значением undefined. console.log(square(5)); // TypeError: square is not a function var square = function(n) { return n * n; }

Аргументы функции не ограничиваются строками и числами. Вы можете передавать целые объекты в функцию. Функция show_props() (объявленная в Работа с объектами) является примером функции, принимающей объекты аргументом.

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

Function factorial(n) { if ((n === 0) || (n === 1)) return 1; else return (n * factorial(n - 1)); }

Затем вы можете вычислить факториалы от одного до пяти следующим образом:

Var a, b, c, d, e; a = factorial(1); // a gets the value 1 b = factorial(2); // b gets the value 2 c = factorial(3); // c gets the value 6 d = factorial(4); // d gets the value 24 e = factorial(5); // e gets the value 120

Есть другие способы вызвать функцию. Существуют частые случаи, когда функции необходимо вызывать динамически, или поменять номера аргументов функции, или необходимо вызвать функцию с привязкой к определенному контексту. Оказывается, что функции сами по себе являются объектами, и эти объекты в свою очередь имеют методы (посмотрите объект ). Один из них это метод , использование которого может достигнуть этой цели.

Область видимости функций

(function scope)

Переменные объявленные в функции не могут быть доступными где-нибудь вне этой функции, поэтому переменные (которые нужны именно для функции) объявляют только в scope функции. При этом функция имеет доступ ко всем переменным и функциям, объявленным внутри её scope. Другими словами функция объявленная в глобальном scope имеет доступ ко всем переменным в глобальном scope. Функция объявленная внутри другой функции ещё имеет доступ и ко всем переменным её родителькой функции и другим переменным, к которым эта родительская функция имеет доступ.

// Следующие переменные объявленны в глобальном scope var num1 = 20, num2 = 3, name = "Chamahk"; // Эта функция объявленна в глобальном scope function multiply() { return num1 * num2; } multiply(); // вернет 60 // Пример вложенной функции function getScore() { var num1 = 2, num2 = 3; function add() { return name + " scored " + (num1 + num2); } return add(); } getScore(); // вернет "Chamahk scored 5"

Scope и стек функции

(function stack)

Рекурсия

Функция может вызывать саму себя. Три способа такого вызова:

  1. по имени функции
  2. по переменной, которая ссылается на функцию

Для примера рассмотрим следующие функцию:

Var foo = function bar() { // statements go here };

Внутри функции (function body ) все следующие вызовы эквивалентны:

  1. bar()
  2. arguments.callee()
  3. foo()

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

Var x = 0; while (x < 10) { // "x < 10" - это условие для цикла // do stuff x++; }

можно было изменить на рекурсивную функцию и вызовом этой функции:

Function loop(x) { if (x >= 10) // "x >= 10" - это условие для конца выполения (тоже самое, что "!(x < 10)") return; // делать что-то loop(x + 1); // рекурсионный вызов } loop(0);

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

Function walkTree(node) { if (node == null) // return; // что-то делаем с элементами for (var i = 0; i < node.childNodes.length; i++) { walkTree(node.childNodes[i]); } }

В сравнении с функцией loop , каждый рекурсивный вызов сам вызывает много рекурсивных вызовов.

Также возможно превращение некоторых рекурсивных алгоритмов в нерекурсивные, но часто их логика очень сложна, и для этого потребуется использование стека (stack ). По факту рекурсия использует stach: function stack.

Поведение stack"а можно увидеть в следующем примере:

Function foo(i) { if (i < 0) return; console.log("begin: " + i); foo(i - 1); console.log("end: " + i); } foo(3); // Output: // begin: 3 // begin: 2 // begin: 1 // begin: 0 // end: 0 // end: 1 // end: 2 // end: 3

Вложенные функции (nested functions) и замыкания (closures)

Вы можете вложить одну функцию в другую. Вложенная функция (nested function ; inner ) приватная (private ) и она помещена в другую функцию (outer ). Так образуется замыкание (closure ). Closure - это выражение (обычно функция), которое может иметь свободные переменные вместе со средой, которая связывает эти переменые (что "закрывает" ("close" ) выражение).

Поскольку вложенная функция это closure, это означает, что вложенная функция может "унаследовать" (inherit ) аргументы и переменные функции, в которую та вложена. Другими словами, вложенная функция содержит scope внешней ("outer" ) функции.

Подведем итог:

  • Вложенная функция имеет доступ ко всем инструкциям внешней функции.
  • Вложенная функция формирует closure: она может использовать аргументы и переменные внешней функции, в то время как внешняя функция не может использовать аргументы и переменные вложенной функции.

Следующий пример показывает вложенную функцию:

Function addSquares(a, b) { function square(x) { return x * x; } return square(a) + square(b); } a = addSquares(2, 3); // возвращает 13 b = addSquares(3, 4); // возвращает 25 c = addSquares(4, 5); // возвращает 41

Поскольку вложенная функция формирует closure, Вы можете вызвать внешную функцию и указать аргументы для обоих функций (для outer и innner).

Function outside(x) { function inside(y) { return x + y; } return inside; } fn_inside = outside(3); // Подумайте над этим: дайте мне функцию, // который передай 3 result = fn_inside(5); // возвращает 8 result1 = outside(3)(5); // возвращает 8

Сохранение переменных

Обратите внимание, значение x сохранилось, когда возвращалось inside . Closure должно сохранять аргументы и переменные во всем scope. Поскольку каждый вызов предоставляет потенциально разные аргументы, создается новый closure для каждого вызова во вне. Память может быть очищена только тогда, когда inside уже возвратился и больше не доступен.

Это не отличается от хранения ссылок в других объектах, но часто менее очевидно, потому что не устанавливаются ссылки напрямую и нельзя посмотреть там.

Несколько уровней вложенности функций (Multiply-nested functions)

Функции можно вкадывать несколько раз, т.е. функция (A) хранит в себе функцию (B), которая хранит в себе функцию (C). Обе фукнкции B и C формируют closures, так B имеет доступ к переменным и аргументам A, и C имеет такой же доступ к B. В добавок, поскольку C имеет такой доступ к B, который имеет такой же доступ к A, C ещё имеет такой же доспут к A. Таким образом cloures может хранить в себе несколько scope; они рекурсивно хранят scope функций, содержащих его. Это называется chaining (chain - цепь ; Почему названо "chaining" будет объяснено позже)

Рассмотрим следующий пример:

Function A(x) { function B(y) { function C(z) { console.log(x + y + z); } C(3); } B(2); } A(1); // в консоле выведится 6 (1 + 2 + 3)

В этом примере C имеет доступ к y функции B и к x функции A . Так получается, потому что:

  1. Функция B формирует closure, включающее A , т.е. B имеет доступ к аргументам и переменным функции A .
  2. Функция C формирует closure, включающее B .
  3. Раз closure функции B включает A , то closure С тоже включает A, C имеет доступ к аргументам и переменным обоих функций B и A . Другими словами, С cвязывает цепью (chain ) scopes функций B и A в таком порядке.

В обратном порядке, однако, это не верно. A не имеет доступ к переменным и аргументам C , потому что A не имеет такой доступ к B . Таким образом, C остается приватным только для B .

Конфликты имен (Name conflicts)

Когда два аргумента или переменных в scope у closure имеют одинаковые имена, происходит конфликт имени (name conflict ). Более вложенный (more inner ) scope имеет приоритет, так самый вложенный scope имеет наивысший приоритет, и наоборот. Это цепочка областей видимости (scope chain ). Самым первым звеном является самый глубокий scope, и наоборот. Рассмотрим следующие:

Function outside() { var x = 5; function inside(x) { return x * 2; } return inside; } outside()(10); // возвращает 20 вместо 10

Конфликт имени произошел в инструкции return x * 2 между параметром x функции inside и переменной x функции outside . Scope chain здесь будет таким: { inside ==> outside ==> глобальный объект (global object )}. Следовательно x функции inside имеет больший приоритет по сравнению с outside , и нам вернулось 20 (= 10 * 2), а не 10 (= 5 * 2).

Замыкания

(Closures)

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

Однако, внешняя функция не имеет доступа к переменным и функциям, объявленным во внутренней функции. Это обеспечивает своего рода инкапсуляцию для переменных внутри вложенной функции.

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

Closure создается, когда вложенная функция как-то стала доступной в неком scope вне внешней функции.

Var pet = function(name) { // Внешняя функция объявила переменную "name" var getName = function() { return name; // Вложенная функция имеет доступ к "name" внешней функции } return getName; // Возвращаем вложенную функцию, тем самым сохраняя доступ // к ней для другого scope } myPet = pet("Vivie"); myPet(); // Возвращается "Vivie", // т.к. даже после выполнения внешней функции // name сохранился для вложенной функции

Более сложный пример представлен ниже. Объект с методами для манипуляции вложенной функции внешней функцией можно вернуть (return ).

Var createPet = function(name) { var sex; return { setName: function(newName) { name = newName; }, getName: function() { return name; }, getSex: function() { return sex; }, setSex: function(newSex) { if(typeof newSex === "string" && (newSex.toLowerCase() === "male" || newSex.toLowerCase() === "female")) { sex = newSex; } } } } var pet = createPet("Vivie"); pet.getName(); // Vivie pet.setName("Oliver"); pet.setSex("male"); pet.getSex(); // male pet.getName(); // Oliver

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

Var getCode = (function() { var apiCode = "0]Eal(eh&2"; // A code we do not want outsiders to be able to modify... return function() { return apiCode; }; }()); getCode(); // Returns the apiCode

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

Var createPet = function(name) { // The outer function defines a variable called "name". return { setName: function(name) { // The enclosed function also defines a variable called "name". name = name; // How do we access the "name" defined by the outer function? } } }

Использование объекта arguments

Объект arguments функции является псевдо-массивом. Внутри функции Вы можете ссылаться к аргументам следующим образом:

Arguments[i]

где i - это порядковый номер аргумента, отсчитывающийся с 0. К первому аргументу, переданному функции, обращаются так arguments . А получить количество всех аргументов - arguments.length .

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

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

Function myConcat(separator) { var result = ""; var i; // iterate through arguments for (i = 1; i < arguments.length; i++) { result += arguments[i] + separator; } return result; }

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

// возвращает "red, orange, blue, " myConcat(", ", "red", "orange", "blue"); // возвращает "elephant; giraffe; lion; cheetah; " myConcat("; ", "elephant", "giraffe", "lion", "cheetah"); // возвращает "sage. basil. oregano. pepper. parsley. " myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");

Т.к. arguments является псевдо-массивом, к нему применимы некоторые методы массивов, например, for .. in

Function func() { for (value in arguments){ console.log(value); } } func(1, 2, 3); // 1 // 2 // 3

Примечание: arguments является псевдо-массивом, но не массивом. Это псевдо-массив, в котором есть пронумерованные индексы и свойство length . Однако он не обладает всеми методами массивов.

Оставшиеся параметры (Rest parameters)

На введение стрелочных функций повлияли два фактора: более короткие функции и лексика this .

Более короткие функции

В некоторый функциональных паттернах приветствуется использование более коротких функций. Сравните:

Var a = [ "Hydrogen", "Helium", "Lithium", "Beryllium" ]; var a2 = a.map(function(s) { return s.length; }); console.log(a2); // logs var a3 = a.map(s => s.length); console.log(a3); // logs

Лексика this

До стрелочных функций каждая новая функция определяла свое значение this (новый объект в случае конструктора, undefined в strict mode, контекстный объект, если функция вызвана как метод объекта, и т.д.). Это оказалось раздражающим с точки зрения объектно-орентированного стиля программирования.

Function Person() { // Конструктор Person() определяет `this` как самого себя. this.age = 0; setInterval(function growUp() { // Без strict mode функция growUp() определяет `this` // как global object, который отличается от `this` // определенного конструктором Person(). this.age++; }, 1000); } var p = new Person();

В ECMAScript 3/5 эта проблема была исправлена путем присвоения значения this переменной, которую можно было бы замкнуть.

Function Person() { var self = this; // Некоторые выбирают `that` вместо `self`. // Выберите что-то одно и будьте последовательны. self.age = 0; setInterval(function growUp() { // The callback refers to the `self` variable of which // the value is the expected object. self.age++; }, 1000); }

Смотрите также Function в Справочнике JavaScript для получения дополнительной информации по функции как объекту.




Top