Кратко
Секция статьи "Кратко"Функция — это блок из различных команд. Функции служат для упорядочивания и структурирования кода программы.
Примеры простейших встроенных функций — это alert
и prompt
, но можно создавать и свои 🤘🏼
Как пишется
Секция статьи "Как пишется"Базовый вариант создания функции:
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" })