Функция

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

Время чтения: 6 мин

Кратко

Секция статьи "Кратко"

Функция — это блок из различных команд. С ней легко создавать порядок в коде программы, избавляясь от ненужных повторений и запутанных частей.

Как пишется

Секция статьи "Как пишется"

Первый способ — просто объявить функцию в коде (по-английски Function Declaration):

        
          
          function hello(name) {  alert(`Привет ${name} 😊`)}
          function hello(name) {
  alert(`Привет ${name} 😊`)
}

        
        
          
        
      

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

        
          
          const hello = function(name) {  alert(`Привет ${name} 😊`)}
          const hello = function(name) {
  alert(`Привет ${name} 😊`)
}

        
        
          
        
      
Открыть демо в новой вкладке

Способы написать функцию из примеров выше не одно и тоже (хотя и выглядят почти одинаково 🤔). Основное отличие в том, что если мы использовали Function Declaration, то JavaScript перенесёт функции вверх текущей области видимости. Это называется «всплытие» (или hoisting).

На практике это означает, что мы можем использовать её до своего же объявления. Пишем — заработай, и где-то потом объясняем как. Магия!

        
          
          hello('Иван')function hello(name) {  alert(`Привет ${name} 😊`)}
          hello('Иван')

function hello(name) {
  alert(`Привет ${name} 😊`)
}

        
        
          
        
      

Использование Function Expression вызовет ошибку:

        
          
          hello('Иван')const hello = function (name) {  alert(`Привет ${name} 😊`)}// hello is not a function
          hello('Иван')

const hello = function (name) {
  alert(`Привет ${name} 😊`)
}

// hello is not a function

        
        
          
        
      

Как понять

Секция статьи "Как понять"

Объявление функции расшифровывается так:

  • В начале идёт ключевое слово function, чтобы заявить о наших намерениях объявить функцию;
  • Затем имя функции, чтобы можно было отличить одну функцию от другой (у нас лаконичное hello, но бывает лаконичное ничего...);
  • В круглых скобках мы указываем параметры (можно и без), которые передадим внутрь;
  • Наконец, тело функции — это код в фигурных скобках, который выполняется при её вызове.

Вызвать функцию ещё проще. Создадим новую и назовём её makeShawarma:

        
          
          function makeShawarma(meat) {  alert(`Ваша шаурма с ${meat} готова 🌯`)}
          function makeShawarma(meat) {
  alert(`Ваша шаурма с ${meat} готова 🌯`)
}

        
        
          
        
      

Для вызова сперва пишем имя функции, а затем в круглых скобках указываем аргумент (или аргументы), например, слово курочкой. Мы объявляем: запусти makeShawarma с курочкой внутри.

        
          
          makeShawarma('курочкой')
          makeShawarma('курочкой')

        
        
          
        
      
Открыть демо в новой вкладке

Имя функции

Секция статьи "Имя функции"

Функцию стоит называть так, чтобы название объясняло её действие. Так другим людям понятнее читать код, а вам не придётся вспоминать или разбираться, что такое таинственный function IgorMishaPasha123321() 🤔. Это же правило касается и переменных: передаём имя — называем name.

В JavaScript есть два типа функций по признаку имени. В примере ниже функция называется именованной, потому что у неё есть имя.

        
          
          function namedFunction() {}
          function namedFunction() {}

        
        
          
        
      

Противоположность именованным функциям — анонимные. У таких имени нет:

        
          
          function() {}
          function() {}

        
        
          
        
      

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

        
          
          function functionA() {  function functionB() {    throw new Error('Ошибочка!')  }  functionB()}functionA()// Error: Ошибочка!//    at functionB (/index.js:3:11)//    at functionA (/index.js:6:3)
          function functionA() {
  function functionB() {
    throw new Error('Ошибочка!')
  }

  functionB()
}

functionA()

// Error: Ошибочка!
//    at functionB (/index.js:3:11)
//    at functionA (/index.js:6:3)

        
        
          
        
      

Здесь видно, какие функции вызывали какие, и что привело к ошибке, вплоть до номера строки и символа. С анонимными сложнее, поскольку вместо имён функций будут лишь номера строк.

Параметры

Секция статьи "Параметры"

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

Например, функция showMessage принимает два параметра под названиями user и message, а потом соединяет их для целого сообщения.

        
          
          function showMessage(user, message) {  console.log(user + ': ' + message)}
          function showMessage(user, message) {
  console.log(user + ': ' + message)
}

        
        
          
        
      

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

        
          
          showMessage('Маша', 'Привет!')// Маша: Привет!showMessage('Иван', 'Как делишки?')// Иван: Как делишки?
          showMessage('Маша', 'Привет!')
// Маша: Привет!

showMessage('Иван', 'Как делишки?')
// Иван: Как делишки?

        
        
          
        
      

Функция и переменные

Секция статьи "Функция и переменные"

Переменные внутри функции существуют только внутри этой функции — этот эффект называется областью видимости.

        
          
          function five() {  const numberFive = 5}console.log(numberFive)//numberFive is not defined
          function five() {
  const numberFive = 5
}

console.log(numberFive)
//numberFive is not defined

        
        
          
        
      

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

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

        
          
          const numberFour = 4function five() {  const numberFive = numberFour + 1  return numberFive}console.log(numberFour)// 4console.log(five())// 5console.log(numberFive)// numberFive is not defined
          const numberFour = 4

function five() {
  const numberFive = numberFour + 1
  return numberFive
}

console.log(numberFour)
// 4
console.log(five())
// 5
console.log(numberFive)
// numberFive is not defined

        
        
          
        
      

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

💡 В примере выше было ключевое слово «return». Что это такое и для чего нужно — более подробно раскрыто в отдельной статье про return 😎

Стрелочные функции

Секция статьи "Стрелочные функции"

Стрелочная функция записывается намного короче, чем обычная. В самой простой записи ключевое слово function и фигурные скобки не требуются.

        
          
          const divider = (number) => number / 2
          const divider = (number) => number / 2

        
        
          
        
      

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

        
          
          const divider = (numerator, denominator) => {  const result = numerator / denominator  return result}
          const divider = (numerator, denominator) => {
  const result = numerator / denominator
  return result
}

        
        
          
        
      

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

Рекурсивные функции

Секция статьи "Рекурсивные функции"

Внутри функции можно вызывать её саму — это пример рекурсивной функции.

        
          
          function fac(n) {  if (n < 2) {    return 1  } else {    return n * fac(n - 1)  }}console.log(fac(3))// 6
          function fac(n) {
  if (n < 2) {
    return 1
  } else {
    return n * fac(n - 1)
  }
}

console.log(fac(3))
// 6

        
        
          
        
      

Если разложить пример, то получится следующая цепочка:

  • fac(3) это 3 * fac(2);
  • fac(2) это 2 * fac(1);
  • fac(1) это 1.

Получается, что fac(3) это 3 * 2 * 1, то есть 6. Такой подход часто применяется в математических операциях, но не ограничивается ими.

Контекст функции

Секция статьи "Контекст функции"

У кода в момент выполнения есть «окружение». Это функция, которая сейчас отрабатывает, это содержащиеся в ней переменные, это глобальные переменные. Всё это и есть контекст.

🐌 Контекст это сложная, но очень важная тема, поэтому мы написали об этом отдельную статью.

На практике

Секция статьи "На практике"

Дока Дог советует

Секция статьи "Дока Дог советует"

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

Открыть демо в новой вкладке

🛠 Давайте функциям имена, чтобы отладку проводить было проще.

Анонимную функцию будет сложнее отлаживать, потому что в стеке вызовов не будет её имени.

        
          
          someElement.addEventListener('click', function () {  throw new Error('Error when clicked!')})
          someElement.addEventListener('click', function () {
  throw new Error('Error when clicked!')
})

        
        
          
        
      

В отличие от именованной:

        
          
          someElement.addEventListener('click', function someElementClickHandler() {  throw new Error('Error when clicked!')})
          someElement.addEventListener('click', function someElementClickHandler() {
  throw new Error('Error when clicked!')
})

        
        
          
        
      

🛠 У стрелочных функций можно использовать неявный (implicit) return:

        
          
          const arrowFunc1 = () => {  return 42}const arrowFunc2 = () => 42arrowFunc1() === arrowFunc2()// true// Обе функции возвращают 42
          const arrowFunc1 = () => {
  return 42
}

const arrowFunc2 = () => 42

arrowFunc1() === arrowFunc2()
// true
// Обе функции возвращают 42

        
        
          
        
      

Также можно возвращать любые структуры и типы данных:

        
          
          const arrowFunc3 = () => 'строка'const arrowFunc4 = () => ['массив', 'из', 'строк']
          const arrowFunc3 = () => 'строка'
const arrowFunc4 = () => ['массив', 'из', 'строк']

        
        
          
        
      

Чтобы вернуть объект, его необходимо обернуть в скобки. Только так JS поймёт, что мы не открываем тело функции, а возвращаем результат:

        
          
          const arrowFunc5 = () => ({ cat: 'Барс' })console.log(arrowFunc5())// { cat: 'Барс' }
          const arrowFunc5 = () => ({ cat: 'Барс' })

console.log(arrowFunc5())
// { cat: 'Барс' }

        
        
          
        
      

Алексей Никитченко советует

Секция статьи "Алексей Никитченко советует"

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

        
          
          [1, 2, 3, 4, 5].map(function (num) {  return num * 2})
          [1, 2, 3, 4, 5].map(function (num) {
  return num * 2
})

        
        
          
        
      

Или в вызов другой функции:

        
          
          function makeCouple(recipe) {  const green = '🍏'  const red = '🍎'  return recipe(green, red)}const result = makeCouple(function(one, two) { return one + two })console.log(result)//🍏🍎
          function makeCouple(recipe) {
  const green = '🍏'
  const red = '🍎'
  return recipe(green, red)
}

const result = makeCouple(function(one, two) { return one + two })
console.log(result)
//🍏🍎

        
        
          
        
      

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

На собеседовании

Секция статьи "На собеседовании"

Это партнёрская рубрика, мы выпускаем её совместно с сервисом онлайн-образования Яндекс Практикум. Приносите вопрос, на который не знаете ответа, в задачи, мы разложим всё по полочкам и опубликуем. Если знаете ответ, присылайте пулреквест на GitHub.

Это вопрос без ответа. Вы можете помочь! Почитайте о том, как контрибьютить в Доку.

Это вопрос без ответа. Вы можете помочь! Почитайте о том, как контрибьютить в Доку.

Это вопрос без ответа. Вы можете помочь! Почитайте о том, как контрибьютить в Доку.

Andrei Kalpovskii  отвечает

Секция статьи "Andrei Kalpovskii  отвечает"

IIFE (Immediately Invoked Function Expression) – это функция, которая выполняется сразу же после того, как была определена.

Записывается так:

        
          
          (function () {    // какие-то действия})();
          (function () {
    // какие-то действия
})();

        
        
          
        
      

IIFE состоит из двух частей:

  • Функция с лексической областью видимости, заключённая в круглые скобки
  • Мгновенно выполняющееся функциональное выражение ()

Функция внутри скобок создаёт внутри себя область видимости, доступ к которой есть только у неё. Всё, что внутри функции, остаётся только внутри.

Примеры использования:
Секция статьи "Примеры использования:"

Используя IIFE, можно не бояться конфликтов имён переменных.

        
          
          (function () {    let name = "Дока Дог";    console.log(name);})();(function () {    let name = "Гав-Гав";    alert(name);})();// Никаких конфликтов
          (function () {
    let name = "Дока Дог";
    console.log(name);
})();

(function () {
    let name = "Гав-Гав";
    alert(name);
})();

// Никаких конфликтов


        
        
          
        
      

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

        
          
          let result = (function () {    let name = "Дока Дог";    return name;})();console.log(result); //Дока Дог
          let result = (function () {
    let name = "Дока Дог";
    return name;
})();

console.log(result); //Дока Дог

        
        
          
        
      
Часто вам могут задать такой вопрос:
Секция статьи "Часто вам могут задать такой вопрос:"

Является ли это IIFE?

        
          
          function(){}();
          function(){}();

        
        
          
        
      

Ответ: нет, не является.
Результатом парсинга такого выражения будет function declaration и отдельно стоящий ()