Кратко
СкопированоФункция — это блок из различных команд. С ней легко создавать порядок в коде программы, избавляясь от ненужных повторений и запутанных частей.
Как пишется
СкопированоПервый способ — просто объявить функцию в коде (по-английски 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
, но бывает лаконичное ничего...); - В круглых скобках мы указываем параметры (можно и без), которые передадим внутрь;
- Наконец, тело функции — это код в фигурных скобках, который выполняется при её вызове.
Вызвать функцию ещё проще. Создадим новую и назовём её make
:
function makeShawarma(meat) { alert(`Ваша шаурма с ${meat} готова 🌯`)}
function makeShawarma(meat) { alert(`Ваша шаурма с ${meat} готова 🌯`) }
Для вызова сперва пишем имя функции, а затем в круглых скобках указываем аргумент (или аргументы), например, слово курочкой
. Мы объявляем: запусти make
с курочкой
внутри.
makeShawarma('курочкой')
makeShawarma('курочкой')
Имя функции
СкопированоФункцию стоит называть так, чтобы название объясняло её действие. Так другим людям понятнее читать код, а вам не придётся вспоминать или разбираться, что такое таинственный function
🤔. Это же правило касается и переменных: передаём имя — называем 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)
Здесь видно, какие функции вызывали какие, и что привело к ошибке, вплоть до номера строки и символа. С анонимными сложнее, поскольку вместо имён функций будут лишь номера строк.
Параметры
СкопированоПри вызове функции можно передать данные, они будут использованы кодом внутри.
Например, функция show
принимает два параметра под названиями 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
Если пытаться вызвать их снаружи, то возникнет ошибка. В примере выше мы увидим, что number
не задан, поскольку вне функции мы действительно не задали number
.
В то же время глобальные переменные можно использовать как снаружи функции, так и внутри:
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
Вызов глобальной переменной number
не приводит к ошибке, тогда как переменная number
по-прежнему существует только внутри функции.
💡 В примере выше было ключевое слово «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 * 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) //🍏🍎
В примерах выше мы не объявляем переданную функцию заранее, не даём ей имя, потому что зачем, если в итоге она отработает единожды? Практичнее объявить и сразу использовать где нужно, для чего анонимные функции отлично подходят.
На собеседовании
СкопированоЭто вопрос без ответа. Вы можете помочь! Почитайте о том, как контрибьютить в Доку.
Это вопрос без ответа. Вы можете помочь! Почитайте о том, как контрибьютить в Доку.
отвечает
СкопированоВ первом случае просто была вызвана функция, которая ничего не возвращает. Значение переменной будет равно 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
используется срабатывают два правила:
- При вызове
return
с объектом, вместоthis
вернётся этот объект. - При вызове
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; }; };
отвечает
Скопировано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
и отдельно стоящий (