Функции в JavaScript являются очень важной составляющей языка. Фактически большая часть программирования на JS - это код, обернутый в функцию.
Функции создаются для того, чтобы уменьшить количество кода. Любой код, который выполняется у вас на странице хотя бы дважды, уже достоин того, чтобы стать функцией. Поэтому можно написать такое определение функции в JavaScript:
Замечательно то, что функция НЕ выполнится, если не будет вызвана.
Записываются функции в общем виде следующем виде:
1 2 3 4 5 6 7 | function funcName(){ //тело функции //весь код записывается здесь } //вызов функции funcName(); |
К именам функций существуют те же требования, что и к именам переменных. Важно помнить, что функция - это особый вид переменных в JavaScript, поэтому задавать одинаковые имена для функции и для переменной НЕЛЬЗЯ.
Например, нам нужна функция, которая печатает в тело документа сегодняшнее число.
1 2 3 4 5 6 | function today(){ var day = new Date(); document.write(day.toLocaleString()); } today(); |
В JavaScript принято сначала объявлять функцию, а потом уже ее вызывать.
Обязательны и круглые скобки после имени функции. Они могут оставаться пустыми, а могут содержать аргументы функции. Например, функция выводит нам приветствие в диалоговом окне alert():
1 2 3 4 | function sayHello(){ alert("Привет!"); } sayHello(); |
Но, предположим мы хотим, чтобы приветствие было целевым. Например, пользователь вводит свое имя, а функция его получает.
1 2 3 4 5 6 | function sayHello(text){ alert("Привет,"+text+"!"); } var name = prompt("Введите свое имя", "Виктор"); sayHello(name); |
Что можно записывать в функциях JavaScript
Функции в своем коде могут иметь различные конструкции, характерные для языка JavaScript:
- объявление переменных (
var, let, const
); - арифметические операции (сложение, вычитание и т.д.);
- условные конструкции -
if...else
,switch...case
, тернарный оператор; - любые виды циклов -
for()
,while()
,do...while()
,for...in
; - вызов других функций.
Например, функция должна нам построить таблицу в зависимости от переданных аргументов - количества строк и столбцов. Мы будем использовать в коде функции объявление переменной table и вложенные циклы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function createtable(rows, cols){ var table="<table style='width: 600px' border='1'>"; for(var i=0; i<rows; i++){ table +="<tr>"; for(var j=0; j<cols; j++){ table +="<td> Ячейка "+(j+1)+" </td>"; } table +="</tr>"; } table +="</table>"; document.write(table); } createtable(5, 3); createtable(3, 5); |
А следующая функция будет проверять четность/нечетность числа, введенного пользователем:
1 2 3 4 5 6 | function chet_nechet(num){ if(isNaN(num)) alert("Это не число"); else alert( num%2 == 0 ? "Число четное" : "Число нечетное" ); } chet_nechet(parseInt(prompt("Введите число"))); |
В функции использована условная конструкция if...else
и тернарный оператор.
Оператор return
Оператор return позволяет вернуть значение из функции, т.е. получить результат для дальнейшего использования в коде. Этот результат можно записать (присвоить) в какую-либо переменную, использовать в дальнейшем коде для вычислений, например, или вывести на html-страницу.
С помощью return можно возвращать логические значения (true
или false
), результат вычислений (число), строку или даже объект. Следует понимать 2 особенности, связанные с этим оператором:
- вернуть можно только одно значение (если нужно несколько, выходом может быть объект в качестве возвращаемого значения);
- после выполнения этого оператора прекращается выполнение кода функции. Дальше будет выполняться код, который следует за вызовом функции.
Рассмотрим простой пример. У нас есть функция, которая считает сумму 2-х чисел. Можно использовать ее для того, чтобы сложить несколько чисел и суммы этих чисел. Вот код:
1 2 3 4 5 6 7 | function summa(a,b){ return a+b; } var sum1 = summa(3,6); // вернет 9 (3+6) var sum2 = summa(10, 12); // вернет 22 (10+12) alert(summa (sum1, sum2)); // выведет 31 (22+9) |
Рассмотрим пример посложнее. Нам необходимо создать объект на html-странице, причем не один, а несколько (в примере будет 2 - div и абзац). Выполняться это будет в функции. Затем в основном коде для каждого из элементов задается класс, описанный в стилях.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | <style> .pink { background-color: pink; color: #6a0462; padding: 7px; width: 80%; border: 1px dotted #6a0462; margin: 10px auto; } .par { margin: 10px; font-style: italic; } </style> <script> function createElem(type, text, parent) { var elem = document.createElement(type); elem.innerHTML = text; parent.appendChild(elem); return elem; } var test = document.getElementById("test"); var div = createElem("div", "Новый блок", test); var p = createElem("p", "Абзац в нашем блоке", div); div.className = "pink"; p.className = "par"; </script> |
В этом примере возвращается из функции объект - ссылка на создаваемый html-элемент. И поскольку это особый объект, который встраивается в страницу, мы можем назначить для него форматирование с помощью классов или id, записав в переменную ссылку на этот объект. Для других объектов это будет другой код.
Примечание: данный скрипт должен быть размещен в body, и перед тегом <script> должен быть создан <div id="test"></div>
Кстати, в первом примере с функцией суммы есть интересный момент, связанный со строками: если вызвать эту функцию, передав ей в качестве аргументов строки, то мы получим их склеивание (конкатенацию), а не результат сложения. Пример:
1 2 3 4 5 6 7 | function summa(a,b){ return a+b; } var sum1 = summa("Вася ", " Пупкин"); // вернет "Вася Пупкин" var sum2 = summa(" учится в школе ", 122); // вернет " учится в школе 122" alert(summa (sum1, sum2)); // выведет "Вася Пупкин учится в школе 122" |
Имеет смысл изменить код этой функции так, чтобы подсчет велся только в случае чисел. Для этого введем условие и дважды используем оператор return:
1 2 3 4 5 6 7 8 9 10 11 | function summa(a,b){ if (isNaN(a) || isNaN(b)){ alert("Вводить нужно было только числа"); return 0; } return a+b; } var num1 = +prompt("Введите первое число", "9"); var num2 = +prompt("Введите второе число", "111"); alert(summa (num1, num2)); |
Здесь оператор "+" преобразует строку в число, если строка вида "12", "-14" и т.д. В случае строки "abcd12" происходит преобразование в тип NaN (Not a Number) и вычисление суммы не выполняется. Выводится сообщение об ошибке и возвращается 0 (ноль). Обратите внимание, что после оператора return выполнение функции прекращается.
Кстати, функция, которая не имеет оператора return, на самом деле возвращает значение undefined.
Аргументы функций в Javascript. Свойство функции arguments
В JavaScript существует псевдомассив аргументов функции, который так и называется - arguments. И, если точное количество аргументов неизвестно, то можно воспользоваться этим массивом. Он имеет свойство length, как и обычные массивы, но методы массивов push()
, pop()
и т.д. вы к нему применить не сможете.
Рассмотрим использование этого массива на примере функции суммы:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function summa() { var sum = 0, str = ""; console.log(arguments); for (var i = 0; i < arguments.length; i++) { sum += arguments[i]; str += arguments[i] + ","; } str=str.slice(0,-1); return str+"="+sum; } console.log(summa(1, 2, 3)); //запишет в консоли [1,2,3]; document.write("<p> Сумма чисел " + summa(2, 3, 56, 17, 34, 67) + "</p>"); |
Параметры по умолчанию в функции
Еще одна особенность связана с параметрами по умолчанию. Вернемся к примеру с созданием таблицы. Предположим, пользователь захотел вызвать функцию без аргументов:
1 | createtable(); |
Но в этом случае ничего не получится, т.к. ни количества строк, ни количества столбцов у нас нет. Для того чтобы это изменить, мы можем воспользоваться таким подходом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function createTable(rows, cols){ if(rows === undefined) rows = 2; cols = cols || 2; var table="<table style='width: 600px' border='1'>"; for(var i=0; i<rows; i++){ table +="<tr>"; for(var j=0; j<cols; j++){ table +="<td> Ячейка "+(j+1)+" </td>"; } table +="</tr>"; } table +="</table>"; document.write(table); } createTable(); |
В первой строчке сценария функции мы проверяем, а существует ли значение для переменной row, и если она равна undefined, т.е. значения нет, то присваиваем ей значение 2. Во второй строке можно присвоить переменной cols
одно из значений - либо переданное в функцию, либо, если параметр не был передан, т.е. он равен undefined, мы назначаем опять-таки значение 2.
То же самое можно написать другим способом - если эти параметры по умолчанию подставить в скобках при вызове функции в операции присваивания. Важно помнить, что этот способ будет работать только в современных браузерах. В более старых придется воспользоваться методом, приведенным выше.
1 2 3 4 5 6 7 8 9 10 11 12 13 | function createTable2(rows = 3, cols = 3){ var table="<table style='width: 600px' border='1'>"; for(var i=0; i<rows; i++){ table +="<tr>"; for(var j=0; j<cols; j++){ table +="<td> Ячейка "+(j+1)+" </td>"; } table +="</tr>"; } table +="</table>"; document.write(table); } createTable2(); |
Типы функций в Javascript
Все функции, которые мы рассматривали до сих пор, относятся к категории Function Declaration, т.е. функции объявленной. Кроме того, существует тип функций Function Expression, т.е. функции-выражения, или функциональные выражения. Посмотрите на разницу между их объявлением:
1 2 3 4 5 6 7 8 9 | // Function Declaration function testNum(num){ return num > 0 ? true : false ; } // Function Expression var testNum = function(num){ return num > 0 ? true : false ; } |
Function Expression представляет собой переменную, в которую записывается код функции. Чаще всего такой вариант применяют для использования функции в качестве метода объекта.
Если вы вызываете любую из этих функций после ее объявления, то разницы вы не заметите. Но дело заключается в том, что иногда нужно вставить код вызова функции в другую функцию, причем описана она будет раньше, чем та, которую вызывают. Понятней будет на примере:
1 2 3 4 5 6 7 8 9 10 11 | func_declaration(); // код сработает, выведется диалоговое окно с сообщением function func_declaration() { alert ("Привет из function declaration!"); } func_expression(); // в FireFox выведет в консоль TypeError: func_expression is not a function var func_expression = function () { alert ("Салют из function expression!"); } |
Для того чтобы увидеть ошибку, нужно открыть консоль браузера (F12 и Esc).
Так вот в этом простом примере мы не увидели второго окна alert()
, т.к. функция func_expression()
была вызвана ДО ее объявления. И это вызвало ошибку интерпретатора, т.к. Function expression
НЕ вычисляется до выполнения javascript, из-за того, что является операцией присваивания переменной. Поэтому вызов function expression
нельзя осуществлять до объявления самой функции, так как переменная на тот момент содержит undefined
. Именно поэтому хорошим стилем кода на JavaScript является объявление всех функций в верхней части скрипта.
Что же касается Function declaration, то Javascript выявляет все определения таких функций и переносит их в начало сценария (этот процесс называется hoisting, или поднятие функций). Т.е. если в коде присутствуют такие функции, то JavaScript знает о них еще до выполнения сценария. Именно поэтому не важно, в каком месте кода вызывается Function declaration. Но все-таки, если есть возможность, объявляйте функции в начале сценария.
Области видимости переменных
Когда вы создаете переменные в своем основном коде, они видны везде - и в циклах, и в условных конструкциях, и в функциях. Но переменные, созданные внутри функции с помощью ключевого слова var НЕ видны, т.е. неизвестны за пределами этой функции. Т.е. при попытке обратиться к ним будет выдана ошибка, т.к. переменная не существует.
В примере ниже глобальная переменная a
перезаписывается внутри функции func()
, а затем и во внутренней функции innerFunc()
. А это далеко не всегда приводит к нужным результатам в процессе выполнения кода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | //Глобальная переменная var a=1; console.log(a); var func = function(){ //локальная переменная var a = 5; console.log(a); var innerFunc = function(){ var a = 20; console.log(a); } innerFunc(); } func(); |
Если закомментировать вызов функции console.log()
в каждой из функций, можно увидеть, чему будет равно значение переменной a
. На данный момент будут выведено 3 числа последовательно: 1, 5 и 20.
Рекурсия
Рекурсия, или рекурсивная функция - это функция, которая вызывает сама себя до определенного момента. Этот момент определяется неким условием, после которого из функции, как правило, возвращается результат.
Классический пример рекурсии - это вычисление чисел Фибоначчи. Что это такое? Это последовательность чисел 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711 и т.п., в которой первые два числа равны либо 1 и 1, либо 0 и 1, а каждое последующее число равно сумме двух предыдущих чисел. Названы в честь средневекового математика Леонардо Пизанского, известного как Фибоначчи. Эту последовательность с помощью рекурсии вычисляют так (используем if
):
1 2 3 4 5 6 7 | function fibonacciLong(n) { if (n < 1) return 0; if (n <= 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); } console.log(fibonacciLong(5)); //5 console.log(fibonacciLong(10)); //55 |
или с помощью тернарного оператора:
1 2 3 4 5 | function fibonacci(n) { return n < 1 ? 0 : n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2); } console.log(fibonacci(6)); //8 console.log(fibonacci(11)); //89 |
Эти 2 функции призваны посчитать не только сумму передаваемого числа и предыдущего ему числа, а определить, каким будет 5-е, 10-е или 11-е число в этой последовательности, поэтому считать придется суммы большего количества чисел, чем кажется с первого взгляда. Очень удобно сделать это с помощью рекурсии, т.е. функции, которая вызывает сама себя до тех пор, пока не выполнится второе условие.
Давайте рассмотрим еще один пример с возведением числа в степень. Напомню, что возведение в степень - это фактически умножение числа на само себя несколько раз. По правилам математики, если мы возводим в нулевую степень, то любое число превратится в 1 (360 = 1
). Число же в степени 1 - это всегда то же самое число, т.е. 51 = 5
. Отсюда и ряд условий, проверяемых в функции.
Давайте начнем не с рекурсии, а с функции, которая основана на использовании цикла while
, чтобы понимать механизм формирования условий:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function loopPow(digit, power) { let res = 1; if(digit==1 || power == 0) return res; if(power==1) return digit; while (power > 0) { res *= digit; power--; console.log(res); } return res; } console.log('Число -1 в степени 4 = '+loopPow(-1,4)); console.log('Число 1 в степени 4 = '+loopPow(1,4)); console.log('Число 120 в степени 0 = '+loopPow(120,0)); console.log('Число 5 в степени 1 = '+loopPow(5,1)); |
На скриншоте видно, как проводятся вычисления в функции, если число, переданное в нее не равно 1 или степень не равна 0 или 1. Вычисления производятся только там, где нужно число возводить в степень, большую, чем 0 или 1.
Теперь перепишем эту функцию, используя рекурсию. Как правило, рекурсия использует довольно краткую запись кода, поэтому вместо if
мы будем применять тернарный оператор с несколькими условиями: сначала проверим, не равно ли число 1, затем не равна ли степень 0, затем не равна ли степень 1, и только в случае не выполнения всех условий будем снова вызывать функцию для нашего числа, но со значением степени, уменьшенным на 1.
1 2 3 4 5 6 7 8 9 10 | function recursePow(digit, power) { let res = 1; res = digit== 1? 1: power == 0 ? 1 : power == 1? digit: digit * recursePow(digit, power - 1); console.log(res); return res; } console.log('Число 5 в степени 4 = '+ recursePow(5, 4)); console.log('Число -1 в степени 4 = '+ recursePow(-1, 4)); console.log('Число 1 в степени 4 = '+ recursePow(1, 4)); console.log('Число 5 в степени 1 = '+ recursePow(5, 1)); |
В коде специально использован console.log(res)
для вывода результата при каждом вызове функции, поэтому вычисления будут подобны предыдущим.
Сократим код рекурсии, убрав лишние переменные и console.log()
и получим такой вариант:
1 2 3 4 5 6 | function recurseShortPow(digit, power) { return digit == 1 ? 1 : power == 0 ? 1 : power == 1 ? digit : digit * recursePow(digit, power - 1); } console.log('Число 5 в степени 4 = ' + recurseShortPow(5, 4)); //Число 5 в степени 4 = 625 console.log('Число 12 в степени 1 = ' + recurseShortPow(12, 1)); //Число 12 в степени 1 = 12 |
Возможно, у вас возникнет мысль: "Красиво, но непонятно". И эта мысль вполне уместна, т.к. рекурсии - это довольно сложный вариант написания функций, и ваши мозги должны быть подготовлены к их восприятию наличием некоторого опыта в программировании. То есть пусть вас не пугает сложность этого кода - вы ведь не будете ожидать, что сядете на шпагат, если не приложите усилий для соответствующих тренировок и растяжек? Так вот рекурсия - это что-то вроде шпагата, но для ума - тренироваться надо, - и все получится.
Отдельным блоком среди остальных функций выделяются Стрелочные функции в ES6 - у них есть свои особенности и отличия от принятых в более ранних стандартах EcmaScript. Поскольку они поддерживаются всеми современными браузерами, стоит разобраться с синтаксисом стрелочных функций и областью их применения.
В функциях, которые применяются в JavaScript, существует ряд особенностей. Например, есть такое понятие, как hoisting - всплытие функций, объявленных в виде Function Declaration. Довольно часто используются callback-и - функции обратного вызова, а также замыкания - особая область использования внутренних переменных в функциях. Прочитать об этом подробней можно в статье "Функции в JavaScript. Особенности использования".
Также у функций есть 3 специальных метода: bind(), call() и apply(), которые позволяют управлять контекстом вызова функции - ключевым словом this
.
Хотелось бы еще насчет стрелочных функций получить информацию
Планируется ли материал по стрелочным функциям из нового стандарта?
Планируется. Но точную дату сообщить пока не могу.
Хотелось бы еще каких-то примеров, может, более сложных.
Здравствуйте! очень хороший материал и хотелось бы продолжения про замыкания функций а то я не совсем понимаю что это такое
Спасибо! Материал планируется.
Но на данный момент очень мало времени на подготовку публикаций.
Поэтому будет примерно через месяц, не ранее.
А более сложное использование функций будет.,
Про замыкания, например?