Кратко
СкопированоФункция — это блок из различных команд. С ней легко создавать порядок в коде программы, избавляясь от ненужных повторений и запутанных частей.
Как пишется
СкопированоПервый способ — просто объявить функцию в коде (по-английски 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 = () => ['массив', 'из', 'строк']
Чтобы вернуть объект, его необходимо обернуть в скобки. Только так JavaScript поймёт, что мы не открываем тело функции, а возвращаем результат:
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-и для методов и других функций. Приведу несколько примеров:
- 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]
- Обработчик события для HTML элемента. Стоит обратить внимание, что в таком случае не получится удалить его через
remove
.Event Listener
document.getElementById('buttonId').addEventListener('click', () => { console.log('Кнопка была нажата!');});
document.getElementById('buttonId').addEventListener('click', () => { console.log('Кнопка была нажата!'); });
- Замыкания:
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
- Таймер:
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
используется срабатывают два правила:
- При вызове
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; }; };
отвечает
СкопированоФункция высшего порядка (Higher Order Function) — это функция, которая принимает в качестве аргумента другие функции и/или возвращает в результате своей работы функцию.
Функции высшего порядка нужны, чтобы создавать более гибкий код, строить абстракции и применять паттерны функционального программирования.
Рассмотрим несколько примеров.
set
— глобальная функция высшего порядка, так как принимает в качестве аргумента функцию, которая выполняется с указанной задержкой:
setTimeout( () => {console.log('А вот и я!')}, 5000)
setTimeout( () => {console.log('А вот и я!')}, 5000 )
Многие методы массива являются функциями высшего порядка, так как принимают в качестве аргумента колбэк-функции, которые перебирают элементы массива. Например, метод .map
принимает в качестве аргумента функцию, которая преобразовывает каждый элемент в массиве:
const years = [1970, 1990, 1995]const objects = years.map(item => { return { year: item }})console.log(objects)// [ { year: 1970 }, { year: 1990 }, { year: 1995 } ]
const years = [1970, 1990, 1995] const objects = years.map(item => { return { year: item } }) console.log(objects) // [ { year: 1970 }, { year: 1990 }, { year: 1995 } ]
Функции высшего порядка используют для реализации подхода «частичное применение функции». В нём функция при первом вызове принимает только часть нужных аргументов и возвращает новую функцию, пока ждёт остальные аргументы. Такой подход строится на механизме замыкания.
Рассмотрим как работает реализация подхода на примере. Создадим простую функцию логирования. Она принимает имя источника сообщения и текст сообщения в качестве аргументов:
const log = (sourceName, message) => { console.log(sourceName,':', message)}
const log = (sourceName, message) => { console.log(sourceName,':', message) }
Такую функцию не удобно многократно использовать для одного и того же источника сообщений, так как придётся повторно указывать первый аргумент:
log('Модуль A', 'Запуск')// Модуль A: Запускlog('Модуль A', 'Проведено тестирование грунта')// Модуль A: Проведено тестирование грунта
log('Модуль A', 'Запуск') // Модуль A: Запуск log('Модуль A', 'Проведено тестирование грунта') // Модуль A: Проведено тестирование грунта
Изменим функцию так, чтобы она умела «запоминать» первый аргумент source
:
// HOF-функция логированияconst log = sourceName => message => { console.log(sourceName,':', message)}
// HOF-функция логирования const log = sourceName => message => { console.log(sourceName,':', message) }
Теперь log
— это функция высшего порядка, так как она возвращает другую функцию.
Полученная в результате вызова log
функция хранит источник сообщений и готова для вызова только с аргументом message
:
const logAppolo = log('Appolo 13')logAppolo('Хьюстон…')// Appolo 13: Хьюстон…logAppolo('Хьюстон, у нас проблема')// Appolo 13: Хьюстон, у нас проблема
const logAppolo = log('Appolo 13') logAppolo('Хьюстон…') // Appolo 13: Хьюстон… logAppolo('Хьюстон, у нас проблема') // Appolo 13: Хьюстон, у нас проблема
отвечает
Скопировано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
и отдельно стоящий (
отвечает
СкопированоОбласть видимости (scope) — это контекст исполнения, в котором значения доступны или, проще говоря, видимы. Когда значение не находится в текущей области видимости, его нельзя использовать напрямую. Однако это ограничение можно обойти используя замыкание (closure).
Области видимости могут иметь иерархию. При этом дочерняя (локальная) область видимости имеет доступ к родительской (глобальной), но не наоборот.
В JavaScrit имеется несколько типов областей видимости:
- глобальная;
- модульная;
- блочная;
- функциональная.
Области видимости позволяют разграничивать доступ к данным, структурировать код. Одним из наиболее интересных примеров использования механизма областей видимости является замыкание (closure).