Объявление функций, аргументы по умолчанию

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

Примеры некоторых функций вы уже знаете – это рассмотренные ранее alert(), prompt() и confirm(). Но мы можем объявить и свои собственные, используя такой синтаксис:

function имя_функции([аргументы]) {
          [тело функции]
}

Здесь имя функции придумывается самим программистом также как и для имен переменных. Но, если имя переменной должно отвечать на вопрос кто, что, то функция – это действие, а значит, ее имя должно соответствовать глаголу, например,

showText, getString, out_log, calcSum, checkForm и т.д.

Приведем простейшее объявление функции, которая выводит в консоль сообщение «Вызов функции»:

function out_log() {
         console.log("Вызов функции");
}

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

out_log();

Если мы напишем еще один вызов:

out_log();

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

Как правило, функции имеют входные параметры. Например, чтобы вывести в консоль произвольную строчку, следует переписать нашу функцию так:

out_log("1-й вызов");
out_log("2-й вызов");
 
function out_log(msg) {
         console.log(msg);
}

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

out_log(1);
out_log(true);

и так далее, мы не ограничены никакими типами. Если функция принимает более одного аргумента, то они разделяются запятыми. Например:

function showMessage(from, text) {
         let msg = from + ": " + text;
         console.log(msg);
}

Со следующими вызовами:

showMessage("Аня", "Привет!");
showMessage("Аня", "Как дела?");

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

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

function showMessage(from, text) {
         let msg = from + " " + email + ": " + text;
         console.log(msg);
}
 
let email = "anna@m.ru";
showMessage("from", "Привет!");
showMessage("from", "Как дела?");

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

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

function showMessage(from, text) {
         email += "+";
         let msg = from + " " + email + ": " + text;
         console.log(msg);
}

При повторном вызове функции у нас email будет отображаться уже два плюса. То есть, переменная была изменена при первом вызове функции. Но, если объявить такую же переменную внутри функции:

function showMessage(from, text) {
         let email = "+";
         let msg = from + " " + email + ": " + text;
         console.log(msg);
}

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

А что будет, если вызвать showMessage с одним агрументом:

showMessage("from");

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

function showMessage(from, text="Как дела?") {
         let msg = from + " " + email + ": " + text;
         console.log(msg);
}

Обратите внимание, что это будет работать только в новых версиях стандарта JavaScript, в старых версиях приходилось бы делать так:

function showMessage(from, text) {
         if(text === undefined) text ="Как дела?";
         let msg = from + " " + email + ": " + text;
         console.log(msg);
}

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

function abs(x) {
         if(x < 0) x = -x;
         return x;
}
 
let res = abs(-5);
console.log(res);

Мы в этой функции сначала вычисляем модуль переменной x, а затем, с помощью оператора return возвращаем это значение. То есть, оператор return показывает, что должна возвратить функция. Например, если переписать функцию вот так:

function abs(x) {
         if(x < 0) x = -x;
         return 0;
}

то она станет все время возвращать 0 вне зависимости от значения x. Но нам в данной реализации нужно вернуть именно значение x, поэтому пишем return x.

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

function abs(x) {
         return (x < 0) ? -x : x;
}
 
function sum(a, b) {
         return a+b;
}

Одной из частых ошибок, которые делают начинающие программисты – располагают возвращаемое значение на следующей строчке после оператора return:

function abs(x) {
         return
                   (x < 0) ? -x : x;
}

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

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

function abs(x) {
         return (
                   (x < 0) ? -x : x
         );
}

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

function abs(x) {
         return (x < 0) ? -x : x;
         console.log(x);
}

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

function div(a, b) {
         if(b == 0) return Infinity;
         return a/b;
}

Здесь при b=0 деление выполняться не будет, а сразу вернется значение Infinity.

В JavaScript функции всегда возвращают значения: вне зависимости есть или нет в них оператора return. Если оператора нет, то функция возвратит значение undefined:

function out_log() {
         console.log("Вызов функции");
}
 
let res = out_log();
console.log(res);

Или, если просто написать return, будет то же самое:

function out_log() {
         return;
         console.log("Вызов функции");
}

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

function showPrimes(n) {
  nextPrime: for (let i = 2; i < n; i++) {
    for (let j = 2; j < i; j++) {
      if (i % j == 0) continue nextPrime;
    }
 
    console.log( i ); // простое
  }
}

Лучше записать в виде двух функций:

function showPrimes(n) {
  for (let i = 2; i < n; i++) {
    if (!isPrime(i)) continue;
 
    console.log(i);  // простое
  }
}
 
function isPrime(n) {
  for (let i = 2; i < n; i++) {
    if ( n % i == 0) return false;
  }
  return true;
}

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

Вот мы с вами рассмотрели базовые принципы создания собственных функций в JavaScript.

Видео по теме