Функция

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

Время чтения: 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)
//🍏🍎

        
        
          
        
      

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

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

Скопировано
Задать вопрос в рубрику
🤚 Я знаю ответ

Скопировано

Типичными случаями для использования анонимных функций в JavaScript являются замыкания, обработчики событий, таймеры и callback-и для методов и других функций. Приведу несколько примеров:

  1. Callback метода массива:
        
          
          const numbers = [1, 2, 3, 4, 5];const squared = numbers.map(num => num * num);console.log(squared); // [1, 4, 9, 16, 25]
          const numbers = [1, 2, 3, 4, 5];
const squared = numbers.map(num => num * num);

console.log(squared); // [1, 4, 9, 16, 25]

        
        
          
        
      
  1. Обработчик события для HTML элемента. Стоит обратить внимание, что в таком случае не получится удалить его через removeEventListener.
        
          
          document.getElementById('buttonId').addEventListener('click', () => {  console.log('Кнопка была нажата!');});
          document.getElementById('buttonId').addEventListener('click', () => {
  console.log('Кнопка была нажата!');
});

        
        
          
        
      
  1. Замыкания:
        
          
          function createCounter() {  let count = 0;  return function () {    return ++count;  };}const counter = createCounter();console.log(counter()); // 1console.log(counter()); // 2
          function createCounter() {
  let count = 0;

  return function () {
    return ++count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

        
        
          
        
      
  1. Таймер:
        
          
          setTimeout(() => {  console.log('Я сработал через 10 секунд!');}, 10000);
          setTimeout(() => {
  console.log('Я сработал через 10 секунд!');
}, 10000);

        
        
          
        
      
🤚 Я знаю ответ

Алексей Фомин  отвечает

Скопировано

В чём плюс использования стрелочных функций для методов в конструкторе?

Основным плюсом использования стрелочных функций для методов в конструкторе является то, что this будет установлено во время создания функции и this не будет изменяться. Cледовательно, когда конструктор используется для создания нового объекта, this всегда будет ссылаться на этот объект.

        
          
          const Person = function (firstName) {  this.firstName = firstName  this.sayName1 = function () {    console.log(this.firstName)  }  this.sayName2 = () => {    console.log(this.firstName)  }}const ivan = new Person('Иван')const petr = new Person('Пётр')ivan.sayName1.call(petr) // Пётр, т.к call позволяет нам указать, что this будет 'petr' и  this сейчас ссылается на объект petr.ivan.sayName2.call(petr) // Иван, так как стрелочная функция не меняет контекст.
          const Person = function (firstName) {
  this.firstName = firstName

  this.sayName1 = function () {
    console.log(this.firstName)
  }

  this.sayName2 = () => {
    console.log(this.firstName)
  }
}

const ivan = new Person('Иван')
const petr = new Person('Пётр')

ivan.sayName1.call(petr) // Пётр, т.к call позволяет нам указать, что this будет 'petr' и  this сейчас ссылается на объект petr.
ivan.sayName2.call(petr) // Иван, так как стрелочная функция не меняет контекст.

        
        
          
        
      

Любая функци выполняется и создается в рамках контекста выполнения. Внутри этого контекста может или не может быть привязки this.
Когда функция вызывается с указанием перед ней new, также известный как вызов конструктора, привязка this есть. Создается новый объект, объект связывается с [[Прототипом]], this внутри функции связывается с this из текущего (лексического) контекста выполнения.
Мы можем воспользоваться явной привязкой посредством вызова call(..), котроый позволяет нам указать this.
Стрелочные функций заимствуют привязку this из окружающей (функции или глобальной) области видимости и лексическая привязка не может быть изменена.
В стрелочной функции, созданной в Person, привязка не динамическая, а лексическая, поэтому контекст изменить нельзя и this всегда будет ссылаться на этот объект.

🤚 Я знаю ответ

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

🤚 Я знаю ответ

Сергей Власов  отвечает

Скопировано

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

        
          
          const animal = Animal() // ❌console.log(animal) // undefined
          const animal = Animal() // ❌
console.log(animal) // undefined

        
        
          
        
      

Во втором случае перед функцией Animal стоит оператор new. Функция Animal становится конструктором. Она выполняется, но так как this внутри функции не используется, и сама функция ничего не возвращает, то ничего не происходит. Результатом операции становится новый объект, который ссылается на функцию Animal как на конструктор. Этот объект присваивается переменной animal

        
          
          const animal = new Animal() // ✅
          const animal = new Animal() // ✅

        
        
          
        
      

Если Animal имеет вид:

        
          
          function Animal() {  this.name = 'Cat'}
          function Animal() {
  this.name = 'Cat'
}

        
        
          
        
      

То переменная animal, созданная с помощью new, будет иметь доступ к полю name:

        
          
          console.log(animal)// Animal { name: 'Cat' }// Если мы явно не возвращаем ничего из конструктора,// то получаем сам объект в качестве результата.
          console.log(animal)
// Animal { name: 'Cat' }
// Если мы явно не возвращаем ничего из конструктора,
// то получаем сам объект в качестве результата.

        
        
          
        
      

Рассмотрим возврат значения из конструктора

Скопировано

Обычно в функции-конструкторе не используется оператор return. Если return используется срабатывают два правила:

  1. При вызове return с объектом, вместо this вернётся этот объект.
  2. При вызове return с пустым или с примитивным значением, оно будет проигнорировано.

return с объектом возвращает этот объект, во всех остальных случаях возвращается this

        
          
          function Animal() {  this.foo = 'BARBARBAR'  return {    foo: 'bar' // ⬅️ возвращает этот объект  }}const animal = new Animal()console.log(animal.foo)// Вернет `bar`
          function Animal() {
  this.foo = 'BARBARBAR'
  return {
    foo: 'bar' // ⬅️ возвращает этот объект
  }
}

const animal = new Animal()
console.log(animal.foo)
// Вернет `bar`

        
        
          
        
      

А вот пример с примитивом после return:

        
          
          function Animal() {  this.foo = 'BARBARBAR'  return 'bar' // ⬅️ возвращает this}const animal = new Animal()console.log(animal.foo)// Вернет BARBARBAR
          function Animal() {
  this.foo = 'BARBARBAR'
  return 'bar' // ⬅️ возвращает this
}

const animal = new Animal()
console.log(animal.foo)
// Вернет BARBARBAR

        
        
          
        
      
🤚 Я знаю ответ

Скопировано

Объект первого класса (first class object или first class citizen) это объект, который может быть передан как аргумент функции, возвращён из функции или присвоен переменной.

Функции в JavaScript полностью соответствуют этому определению.

Функцию можно присвоить переменной:

        
          
          const multipleTwo = (n) => n * 2;
          const multipleTwo = (n) => n * 2;

        
        
          
        
      

Функция может быть передаваемым аргументом другой функции:

        
          
          async function loadData(func) {  loading = true;  // другой код относящийся к инициализации статусов загрузки  await func();  loading = false;  // другой код относящийся к обработке статуса загрузки}function getData() {  // код получения данных с сервера}loadData(getData);
          async function loadData(func) {
  loading = true;
  // другой код относящийся к инициализации статусов загрузки

  await func();

  loading = false;
  // другой код относящийся к обработке статуса загрузки
}

function getData() {
  // код получения данных с сервера
}

loadData(getData);

        
        
          
        
      

Функции могут быть возвращаемым значением другой функции:

        
          
          function makeAdder(x) {  return function(y) {    return x + y;  };};
          function makeAdder(x) {
  return function(y) {
    return x + y;
  };
};

        
        
          
        
      
🤚 Я знаю ответ

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

🤚 Я знаю ответ

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 и отдельно стоящий ()

🤚 Я знаю ответ

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