Функция

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

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

Кратко

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

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

Примеры простейших встроенных функций — это alert(message) и prompt(message, default), но можно создавать и свои 🤘🏼

Как пишется

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

Базовый вариант создания функции:

        
          
          function hello(name) {  alert("hello" + name)}
          function hello(name) {
  alert("hello" + name)
}

        
        
          
        
      

Как это понять

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

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

Имя функции

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

Функцию стоит называть так, чтобы было ясно, что она делает. Если внутри функции попытаться вызвать эту же функцию — ошибки не произойдёт.

        
          
          var factorial = function fac(n) {  return n < 2 ? 1 : n * fac(n - 1)}console.log(factorial(3))
          var factorial = function fac(n) {
  return n < 2 ? 1 : n * fac(n - 1)
}

console.log(factorial(3))

        
        
          
        
      

Это пример рекурсивной функции.

Рекурсией называется такая конструкция, при которой функция вызывает саму себя. Достаточно часто такое применяется в математических операциях, но не ограничивается ими.

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

Переменные внутри функции существуют только внутри этой функции.

        
          
          const sum = 100function sumOne() {  const sum = 50  return sum}function sumTwo() {  const sum = 75  return sum}alert(sum) // 100alert(sumOne()) // 50alert(sum) // 100alert(sumTwo()) // 75alert(sum) // 100
          const sum = 100
function sumOne() {
  const sum = 50
  return sum
}
function sumTwo() {
  const sum = 75
  return sum
}
alert(sum) // 100
alert(sumOne()) // 50
alert(sum) // 100
alert(sumTwo()) // 75
alert(sum) // 100

        
        
          
        
      

Что это значит? Каждый раз, когда происходит выполнение функции, список доступных для использования переменных меняется. Хотя переменная и называется sum, в каждой функции эта переменная сама по себе, и поэтому изменение переменной в функции sumOne не влияет на глобальную sum и не влияет на локальные (в других функциях) переменные с именем sum.

Почему так происходит? Из-за контекста выполнения функции 😎

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

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

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

Существует 3 типа кода:

  • код функции — код, выполняющийся в теле функции;
  • глобальный код — код, выполняющийся вне рамок какой-либо функции.
  • eval-код — код, выполняющийся внутри функции eval() (использование этой функции — редкость).

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

Параметры

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

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

Например, этот код выводит сообщения:

        
          
          function showMessage(user, message) {  alert(user + ": " + message)}showMessage("Маша", "Привет!")showMessage("Петя", "Привет, Маша, познакомимся?")
          function showMessage(user, message) {
  alert(user + ": " + message)
}
showMessage("Маша", "Привет!")
showMessage("Петя", "Привет, Маша, познакомимся?")

        
        
          
        
      

Разберём код.

Название функции — showMessage.

Она принимает 2 параметра под названиями user и message. Когда описываешь свою функцию — можно называть эти параметры по-своему.

Внутри showMessage идёт вызов встроенной функции alert.

Вызов alert происходит со строкой, состоящей из трёх частей:

  • переменная user;
  • текст: двоеточие и пробел;
  • переменная message.

🤖 Иногда внутри функции требуется поменять значение параметра. Например, добавить форматирование. Пока параметр является примитивом — с этим нет проблем, потому что примитив копируется по значению.

Но в случае не примитивов (например, объектов или массивов) происходит копирование по ссылке. Изменяя параметр, переданный по ссылке, мы изменяем его не только в функции, но и вовне.

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

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

Именованные и анонимные функции

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

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

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

        
        
          
        
      

Это именованная функция, потому что у неё есть имя.

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

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

        
        
          
        
      

Функция без имени — анонимная.

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

        
          
          function functionA() {  function functionB() {    throw new Error("Error!")  }  functionB()}functionA()// Error: Error!//    at functionB (/index.js:3:11)//    at functionA (/index.js:6:3)
          function functionA() {
  function functionB() {
    throw new Error("Error!")
  }

  functionB()
}

functionA()

// Error: Error!
//    at functionB (/index.js:3:11)
//    at functionA (/index.js:6:3)

        
        
          
        
      

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

Если же наши функции были анонимными, то стек вызовов будет не так полезен:

        
          
          ;(function () {  ;(function () {    throw new Error("Error again!")  })()})()// Error: Error again!//    at /index.js:13:11//    at /index.js:14:5//    at /index.js:15:3
          ;(function () {
  ;(function () {
    throw new Error("Error again!")
  })()
})()

// Error: Error again!
//    at /index.js:13:11
//    at /index.js:14:5
//    at /index.js:15:3

        
        
          
        
      

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

Особенно это может быть важно в колбэках — когда мы вызываем какую-то функцию в ответ на событие.

Анонимная функция будет менее полезна при отладке:

        
          
          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!")
})

        
        
          
        
      

Обычные и стрелочные функции

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

Отличие стрелочных функций от обычных в том, что у них нет this. Также стрелочные функции в силу своего синтаксиса анонимны, если не присвоить их переменной.

        
          
          const arrowFunction = () => {}
          const arrowFunction = () => {}

        
        
          
        
      

Стрелочная функция записывается намного короче, чем обычная. Ключевое слово function не требуется, так как сама нотация () => подразумевает функцию.

Если функция ничему не присвоена, то она анонимна:

        
          
          ;(() => {  console.log("Hello world")})()
          ;(() => {
  console.log("Hello world")
})()

        
        
          
        
      

Так как у них нет this, то внутри нельзя получить доступ к arguments:

        
          
          const arrow = () => {  console.log(arguments)}arrow()// ReferenceError: arguments is not defined.
          const arrow = () => {
  console.log(arguments)
}

arrow()
// ReferenceError: arguments is not defined.

        
        
          
        
      

Также из-за отсутствия this их нельзя использовать с new.

Стрелочные функции не могут быть функциями-конструкторами.

        
          
          const Factory = () => {  return {    name: "Arthur",  }}const person = new Factory()// TypeError: Factory is not a constructor.
          const Factory = () => {
  return {
    name: "Arthur",
  }
}

const person = new Factory()
// TypeError: Factory is not a constructor.

        
        
          
        
      

С обычной функцией — порядок:

        
          
          function Factory() {  return {    name: "Arthur",  }}const person = new Factory()
          function Factory() {
  return {
    name: "Arthur",
  }
}

const person = new Factory()

        
        
          
        
      

На практике

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

Дока Дог

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

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

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

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

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

        
          
          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()// Обе функции возвращают 42.
          const arrowFunc1 = () => {
  return 42
}

const arrowFunc2 = () => 42

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

        
        
          
        
      

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

        
          
          const arrowFunc3 = () => "string"const arrowFunc4 = () => ["array", "of", "strings"]
          const arrowFunc3 = () => "string"
const arrowFunc4 = () => ["array", "of", "strings"]

        
        
          
        
      

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

        
          
          const arrowFunc5 = () => ({ some: "object" })
          const arrowFunc5 = () => ({ some: "object" })