July 26, 2022

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 // прежняя тоже работает (почему бы и нет)

Давайте подробно разберём всё, что тут произошло:

  1. Объявление Function Declaration (1) создало функцию и присвоило её значение переменной с именем sayHi.
  2. В строке (2) мы скопировали её значение в переменную func. Обратите внимание (ещё раз): нет круглых скобок после sayHi. Если бы они были, то выражение func = sayHi() записало бы результат вызова sayHi в переменную func, а не саму функцию sayHi.
  3. Теперь функция может быть вызвана с помощью обеих переменных sayHi() и func().

Заметим, что мы могли бы использовать и Function Expression для того, чтобы создать sayHi в первой строке:

let sayHi = function() {
  alert( 'Hello' );
}

let func = sayHi;
// ...

Результат был бы таким же.

Функции-"колбэки"

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

Давайте напишем функцию ask(question, yes, no) с тремя параметрами:

question - Текст вопроса

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

Итого

Стрелочные функции очень удобны для простых действий, особенно для однострочных.

Они бывают двух типов:

  1. Без фигурных скобок: (...args) => expression - правая сторона выражения: функция вычисляет его и возвращает результат. Скобки можно не ставить, если аргумент только один: n => n * 2
  2. С фигурными скобами (...args) => { body } - скобки позволяют нам писать несколько инструкций внутри функции, но при этом необходимо явно вызывать return, чтобы вернуть значение.

Задачи

Перепишите функцию, используя оператор '?' или '||'

Следующая функция возвращает true, если параметр age больше 18.

В ином случае она задаёт вопрос confirm и возвращает его результат.

function checkAge(age) {
  if (age > 18) {
  return true;
  } else {
  return confirm('Родители разрешили?');
  }
}

Перепишите функцию, чтобы она делала то же самое, но без if, в одну строку.

Сделайте два варианта функции checkAge:

  1. Используя оператор ?
  2. Используя оператор ||

Решения:

Оператор ?:

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('Вы отменили выполнение.')
);

Telegram-канал: unknown.dev