Learn JavaScript #10. Функции.
Зачастую нам надо повторять одно и тоже действие во многих частях программы.
Чтобы не повторять один и тот же код во многих местах, придуманы функции.
Функции
Объявление функции
Для создания функций мы можем использовать объявление функции.
function showMessage() { alert('Всем привет!'); }
Вначале идёт ключевое слово function
, после него имя функции(showMessage
), затем список параметров(пустой в примере выше) в круглых скобках через запятую и, наконец, код функции, также называемый "телом функции", внутри фигурных скобок(alert('Всем привет!');
)
function имя(параметры) { ...тело... }
Наша новая функция может быть вызвана по её имени: showMessage()
.
function showMessage() { alert('Всем привет!'); } showMessage(); showMessage();
Вызов showMessage()
выполняет код функции. Здесь мы увидим сообщение дважды.
Если понадобится поменять сообщение или способ его вывода - достаточно изменить его в одном месте: в функции, которая его выводит.
Локальные переменные
Переменные, объявленные внутри функции, видны только внутри этой функции.
function showMessage() { let message = 'Привет, я JavaScript!'; // локальная переменная alert(message); } showMessage(); // Привет, я JavaScript! alert(message); // <- Ошибка, т.к. переменная видна только внутри функции
Внешние переменные
У функции есть доступ к внешним переменным, например:
let userName = 'Vasya'; function showMessage() { let message = 'Hello, ' + userName; alert(message); }
Функция обладает полным доступом к внешним переменным и может изменять их значение.
let userName = 'Vasya'; function showMessage() { userName = 'Petya'; // (1) изменяем значение внешней переменной let message = 'Hello, ' + userName; alert(message); } alert(userName); // Vasya, перед вызовом функции showMessage(); alert(userName); // Petya, значение переменной было изменено функцией
Внешняя переменная используется, только если внутри функции нет такой локальной.
Если одноимённая переменная объявляется внутри функции, тогда она перекрывает внешнюю. Например, в коде ниже функция использует локальную переменную userName
. Внешняя будет проигнорирована:
let userName = 'Vasya'; function showMessage() { let userName = 'Petya'; // объявляем локальную переменную let message = 'Hello, ' + userName; // Petya alert(message); } // функция создаст и будет использовать свою собственную // локальную переменную userName showMessage(); alert(userName); // Vasya, не изменилась, функция не трогала внешнюю переменную
Параметры
Мы можем передать внутрь функции любую информацию, используя параметры (также называемые аргументами функции).
В нижеприведённом примере функции передаются два параметра: from
& text
.
function showMessage(from, text) { alert(from + ': ' + text); } showMessage('Аня', 'Привет!'); // Аня: Привет! (*) showMessage('Аня', 'Как дела?'); // Аня: Как дела? (**)
Когда функция вызывается в строках (*)
и (**)
, переданные значения копируются в локальные переменные from
& text
. Затем они используются в теле функции.
Параметр - это переменная, указанная в круглых скобках в объявлении функции. Аргумент - это значение, которое передаётся функции при её вызове.
Мы объявляем функции со списком параметром, затем вызываем их, передавая аргументы.
Параметры по умолчанию
Если параметр не указан, то его значением становится undefined
.
Например, вышеупомянутая функция showMessage(from, text)
может быть вызвана с одним аргументом:
showMessage('Anya');
Это не приведёт к ошибке. Такой вызов выведет 'Anya: undefined'
. В вызове не указан параметр text
, поэтому предполагается, что text === undefined
.
Если мы захотим задать параметру text
значение по умолчанию, мы должны указать его после =
:
function showMessage(from, text = 'текст не добавлен') { alert(from + ': ' + text); } showMessage('Anya'); // Anya: текст не добавлен
В данном случае "текст не добавлен"
это строка, но на её месте могло бы быть и более сложное выражение, которое бы вычислялось и присваивалось при отсутствии параметра. Например:
function showMessage(from, text = anotherFunction()) { // anotherFunction() выполнится только если не передан text // результатом будет значение text }
Возврат значения
Функция может вернуть результат, который передан в вызвавший её код.
Простейшим примером может служить функция сложения двух чисел:
function sum(a, b) { return a + b; } let result = sum(1, 2); alert(result); // 3
Директива return
может находиться в любом месте тела функции. Как только выполнение доходит до этого места, функция останавливается, и значение возвращается в вызвавший её код (присваивается переменной result
выше).
Вызовов return
может быть несколько, например:
function chechAge(age) { if (age > 18) { return true; } else { return confirm('А родители разрешили?'); } } let age = prompt('Сколько вам лет?', 18); if ( check(age) ) { alert( 'Доступ получен' ); } else { alert( 'Доступ закрыт' ); }
Возможно использовать return
и без значения. Это приведёт к немедленному выходу из функции.
function showMovie(age) { if (!check(age)) { return; } } alert('Вам показвыается кино'); // (*) // ... }
В коде выше, если checkAge(age)
вернёт false
, showMovie
не выполнит alert
.
Выбор имени функции
Функция - это действие. Поэтому имя функции обычно является глаголом. Оно должно быть простым, точным и описывать действие функции, чтобы программист, который будет читать код, получил верное представление о том, что делает функция.
'get...'
- возвращают значение'calc...'
- что-то вычисляют'create...'
- что-то создают'check...'
- что-то проверяют и возвращают логическое значение, и т.д.'show...'
- что-то показывают
showMessage(...) // Показывает сообщение getAge(...) // возвращает возраст (в каком-либо значении) calcSum(...) // вычисляет сумму и возвращает результат createForm(...) // создаёт форму (и обычно возвращает её) checkPermission(...) // проверяет доступ, возвращая true/false
Итого
function имя(параметры, через, запятую) { /* тело, код функции */ }
- Передаваемые значения копируются в параметры функции и становятся локальными переменными.
- Функции имеют доступ к внешним переменным. Но это работает только изнутри наружу. Код вне функции не имеет доступа к локальным переменным.
- Функция может возвращать значение. Если этого не происходит, тогда результат равен
undefined
.
Для того, чтобы сделать код более чистым и понятным, рекомендуется использовать локальные переменные и параметры функций, не пользоваться внешними переменными.
Функция, которая получает параметры, работает с ними и затем возвращает результат, гораздо понятнее функции, вызываемой без параметров, но изменяющей внешние переменные, что чревато побочными эффектами.
- Имя функции должно понятно и чётко отражать, что она делает. Увидев её вызов в коде, вы должны тут же понимать, что она делает, и что возвращает.
- Функция - это действие, поэтому её имя обычно является глаголом.
- Есть много общепринятых префиксов, таких как:
create..., show..., get..., check... и т.д
. Пользуйтесь ими как подсказками, поясняющими, что делает функция.
Функции являются основными строительными блоками скриптом. Мы рассмотрели лишь основы функций в JS, но уже сейчас можем создавать и использовать их.
Function Expression
Функция в JavaScript - это не магическая языковая структура, а особого типа значение.
Синтаксис, который мы использовали до этого, называется Function Declaration
(Объявление Функции):
function sayHi() { alert( 'Hello' ); }
Существует ещё один синтаксис создания функций, который называется Function Expression
(Функциональное Выражение).
let sayHi = function() { alert( 'Hello' ); };
В коде выше функция создаётся и явно присваивается переменной, как любое другое значение. По сути без разницы, как мы определили функцию, это просто значение, хранимое в переменной sayHi
.
Смысл обоих примеров кода одинаков: "создать функцию и поместить её значение в переменную sayHi
".
Мы можем даже вывести это значение с помощью alert:
function sayHi() { alert( 'Hello' ); } alert( sayHi); // выведет код функции, то есть все три строки выше
Конечно, функция - не обычное значение, в том смысле, что мы можем вызвать его при помощи скобок: sayHi()
.
Но всё же это значение. Поэтому мы можем делать с ним то же самое, что и с любым другим значением.
Мы можем скопировать функцию в другую переменную:
function sayHi() { // (1) Создаём alert( 'Hello' ); } let func = sayHi; // (2) Копируем func(); // Hello // (3) вызываем копию (работает)! sayHi(); // Hello // прежняя тоже работает (почему бы и нет)
Давайте подробно разберём всё, что тут произошло:
- Объявление
Function Declaration (1)
создало функцию и присвоило её значение переменной с именемsayHi
. - В строке
(2)
мы скопировали её значение в переменнуюfunc
. Обратите внимание (ещё раз): нет круглых скобок послеsayHi
. Если бы они были, то выражениеfunc = sayHi()
записало бы результат вызоваsayHi
в переменнуюfunc
, а не саму функциюsayHi
. - Теперь функция может быть вызвана с помощью обеих переменных
sayHi()
иfunc()
.
Заметим, что мы могли бы использовать и Function Expression
для того, чтобы создать sayHi
в первой строке:
let sayHi = function() { alert( 'Hello' ); } let func = sayHi; // ...
Функции-"колбэки"
Рассмотрим ещё примеры функциональных выражений и передачи функции как значения.
Давайте напишем функцию ask(question, yes, no)
с тремя параметрами:
yes
- Функция, которая будет вызываться, если ответ будет "Yes"
no
- Функция, которая будет вызываться, если ответ будет "No"
Наша функция должна задать вопрос question
и, в зависимости от того, как ответит пользователь, вызвать yes()
или no()
:
function ask(question, yes, no) { if ( confirm(question) ) yes() else no(); } function showOk() { alert('Вы согласны.'); } function showCancel() { alert('Вы отменили выполнение.'); } /* использование: функции showOk, showCancel передаются в качестве аргументов ask */ ask('Вы согласны?', showOk, showCancel);
На практике подобные функции очень полезны. Основное отличие "реальной" функции ask
от примера выше будет в том, что она использует более сложные способы взаимодействия с пользователем, чем простой вызов окна confirm
. В браузерах такие функции обычно отображают красивые диалоговые окна. Но это уже совсем другая история.
Аргументы функции ask
ещё называют функциями-колбэками или просто колбэками.
Ключевая идея в том, что мы передаём функцию и ожидаем, что она вызовется обратно когда-нибудь позже, если это будет необходимо. В нашем случае, showOk
становится колбэком для ответа "yes", а showCancel
- для ответа "no"
Мы можем переписать этот пример значительно короче, используя Function Expression
:
function ask(question, yes, no) { if ( confirm(question) ) yes() else no(); } ask ( "Вы согласны?", function() { alert('Вы согласились.'); } function() { alert('Вы отменили выполнение.'); } );
Здесь функции объявляются прямо внутри вызова ask(...)
. У них нет имён, поэтому они называются анонимными. Такие функции недоступны снаружи ask
(потому что они не присвоены переменным), но это как раз то, что нам нужно.
Function Expression в сравнении с Function Declaration
Давайте разберём ключевые отличия Function Declaration
от Function Expression
.
Во-первых, синтаксис: как определить, что есть в коде.
Function Declarataion
: функция объявляется отдельной конструкцией"function..."
в основном потоке кода.
// Function Declaration function sum(a, b) { return a + b; }
Fucntion Expression
: функция, созданная внутри другого выражения или синтаксической конструкции. В данном случае функция создаётся в правой части "выражения присваивания"=
:
// Function Expression let sum = function(a, b) { return a + b; }
Более тонкое отличие состоит в том, что Function Expression
создаётся, когда выполнение доходит до него, а затем уже может использоваться.
После того, как потом выполнения достигнет правой части выражения присваивания let sum = function...
- с этого момента функция считается созданной и может быть использована (присвоена переменно, вызвана и т.д).
С Function Declaration
всё иначе.
Function Declaration
можно использовать во всём скрипте (или блоке кода, если функция объявлена в блоке)
Другими словами, когда движок JS готовится выполнять скрипт или блок кода, прежде всего он ищет в нём Function Declaration
и создаёт все такие функции. Можно считать этот процесс "стадией инициализации".
И только после того, как все объявления Function Declaration
будут обработаны, продолжится выполнение.
В результате функции, созданные как Function Declaration
, могут быть вызваны раньше своих определений.
sayHi('Vasya'); // Hello, Vasya function sayHi(name) { alert('Hello, ${name}'); }
Функция sayHi
была создана, когда движок подготавливал скрипт к выполнению, и такая функция видна повсюду в этом скрипте.
...Если бы это было Function Expression
, то такой код вызвал бы ошибку:
sayHi('Vasya'); // ошибка! let sayHi = function(name) { // (*) alert('Hello, ${name}'); };
Функции, объявленные при помощи Function Expresion
, создаются тогда, когда выполнение доходит до них. Это случится только на строке помеченной звёздочкой (*)
. Слишком поздно.
Итого
- Функции - это значения. Они могут быть присвоены, скопированы или объявлены в любом месте кода.
- Если функция объявлена как отдельная инструкция в основном потоке кода, то это
Function Declaration
. - Если функция была создана как часть выражения, то считается, что эта функция объявления при помощи
Function Expression
. Function Declaration
обрабатываются перед выполнением блока кода. Они видны во всём блоке.- Функции, объявленные при помощи
Function Expression
, создаются только тогда, когда поток выполнения достигает их.
В большинстве случаев, когда нам нужно создать функцию, предпочтительно использовать Function Declaration
, так как функция будет видима до своего объявления в коде. Это позволяет более гибко организовывать код и улучшает его читаемость.
Таким образом мы должна прибегать к объявлению функций при помощи Function Expression
в случае, когда синтаксис Function Declaration
не подходит для нашей задачи.
Стрелочные функции, основы
Существует ещё один очень простой и лаконичный синтаксис для создания функций, который часто лучше, чем Function Expression
.
Он называется "функции-стрелки" или "стрелочные функции" (arrow functions
), так как выглядит следующим образом:
let func = (arg1, arg2, ...argN) => expression;
Это создаёт функцию func
, которая принимает аргументы arg1...argN
, затем вычисляет expression
в правой части с их использованием и возвращает результат.
Другими словами это сокращённая версия:
let func = function(arg1, arg2, ...argN) { return expression; };
Давайте рассмотрим конкретный пример:
let sum = (a, b) => a + b; /* Эта стрелочная функция представляет собой более короткую форму: let sum = function(a, b) { return a + b; }; */ alert( sum(1, 2) ); // 3
Как вы можете видеть, (a, b) => a + b
задаёт функцию, которая принимает два аргумента с именами a
& b
. И при выполнении она вычисляет выражение a + b
и возвращает результат.
- Если у нас только один аргумент, то круглые скобки вокруг параметров можно опустить, сделав запись ещё короче:
let double = n => n * 2; // примерно тоже что и: let double = function(n) { return n * 2 } alert( double(3) ); // 6
let sayHi = () => alert('Hello!'); sayHi()
Стрелочные функции можно использовать также, как и Function Expression
.
Например, для динамического создания функции:
let age = prompt('Сколько вам лет?', '18'); let welcome = (age < 18) ? () => alert('Привет!') : () => alert('Здравствуйте!"); welcome();
Поначалу стрелочные функции могут показаться необычными и даже трудночитаемыми, но это быстро пройдёт по мере того, как глаза привыкнут к этим конструкциям.
Они очень удобны для простых однострочных действий, когда лень писать много слов.
Многострочные стрелочные функции
Стрелочные функции, которые мы видели до этого, были очень простыми. Они брали аргументы слева от =>
и вычисляли и возвращали выражение справа.
Иногда нам нужна более сложная функция, с несколькими выражениями и конструкциями. Это также возможно, нужно лишь заключить их в фигурные скобки. При этом важно отличие - в том, что в таких скобках для возврата значения нужно использовать return (как в обычных функциях).
let sum = (a, b) => { // фигурная скобка, открывающая тело функции let result = a + b; return result; // если мы используем скобки, нужно указать "return" }; alert( sum(1, 2) ); // 3
Итого
Стрелочные функции очень удобны для простых действий, особенно для однострочных.
- Без фигурных скобок:
(...args) => expression
- правая сторона выражения: функция вычисляет его и возвращает результат. Скобки можно не ставить, если аргумент только один:n => n * 2
- С фигурными скобами
(...args) => { body }
- скобки позволяют нам писать несколько инструкций внутри функции, но при этом необходимо явно вызыватьreturn
, чтобы вернуть значение.
Задачи
Перепишите функцию, используя оператор '?' или '||'
Следующая функция возвращает true
, если параметр age
больше 18
.
В ином случае она задаёт вопрос confirm
и возвращает его результат.
function checkAge(age) { if (age > 18) { return true; } else { return confirm('Родители разрешили?'); } }
Перепишите функцию, чтобы она делала то же самое, но без if
, в одну строку.
Сделайте два варианта функции checkAge
:
function checkAge(age) { return (age > 18) ? true : confirm('Родители разрешили?'); }
function checkAge(age) { return (age > 18) || confirm('Родители разрешили?'); }
Функция min(a, b)
Напишите функцию min(a, b)
, которая возвращает меньшее из чисел a
& b
.
min(2, 5) == 2 min(3, -1) == -1 min(1, 1) == 1
function min(a, b) { if (a < b) { return a } else { return b } }
Функция pow(x, n)
Напишите функцию pow(x, n)
, которая возвращает x
в степени n
. Иначе говоря, умножает x
на себя n
раз и возвращает результат.
pow(3, 2) = 3 * 3 = 9 pow(3, 3) = 3 * 3 * 3 = 27 и т.д.
Создайте страницу, которая запрашивает x
и n
, а затем выводит результат pow(x, n)
.
P.S В этой задаче функция обязана поддерживать только натуральные значения n
, то есть целые от 1 и выше.
let x = prompt('Укажите число, которое хотите возвести в степень','') let n = prompt('Укажите степень, в которую хотите возвести число', '') if (n > 0 && x % 1 === 0 && n % 1 === 0) { alert(pow(x, n)) } if (x % 1 != 0 || n % 1 != 0) { alert('Поддерживаются только ЦЕЛЫЕ числа') } if (n < 1) { alert('В степени поддерживаются только НАТУРАЛЬНЫЕ числа') } function pow(x, n) { return x ** n } // Замутил чисто по царски через три if`а. Зато работает🙄.
Перепишите с использованием функции-стрелки
Замените код Function Expression
стрелочной функцией:
function ask(question, yes, no) { if ( confirm(question) ) yes() else no(); } ask ( 'Вы согласны?' function() { alert('Вы согласились.'); } function() { alert('Вы отмениили выполнение.'); } );
let ask = function(question, yes, no) { (confirm(question)) ? yes() : no() } ask( 'Вы согласны?', () => alert('Вы согласились.'), () => alert('Вы отменили выполнение.') );