Чем отличаются function declaration, function expression и стрелочная функция?
Вопрос
В чём разница между объявлением функции (function declaration), функциональным выражением (function expression) и стрелочной функцией — особенно в контексте hoisting и this?
Короткий ответ
Function declaration полностью поднимается (hoisting) и доступна до строки объявления. Function expression поднимается только как переменная (без значения) или не поднимается вовсе, если объявлена через const/let. Обе имеют собственные this и arguments. Стрелочная функция не поднимается как вызываемая функция, не имеет собственных this, arguments и super — она берёт их из внешней области видимости.
Подробный ответ
Function declaration — function foo() {}. Движок обрабатывает такие объявления на этапе создания области видимости, до выполнения кода построчно, поэтому функцию можно вызвать выше её объявления в коде.
Function expression — функция, присвоенная переменной: const foo = function() {} (может быть анонимной или именованной). Здесь поднимается только переменная (var foo → undefined, let/const foo → temporal dead zone), но не сама функция как значение. Вызвать её до строки присваивания нельзя.
Стрелочная функция — const foo = () => {} — это всегда expression, никогда не declaration. Ключевое отличие не в хостинге, а в поведении:
- Не имеет собственного
this— берёт его лексически, из окружающего кода на момент создания. - Не имеет собственного объекта
arguments— при обращении берётсяargumentsиз внешней функции (или ошибка, если её нет). - Не может быть использована как конструктор (
new (() => {})()броситTypeError). - Не имеет
prototype. - Не может быть привязана через
bind/call/applyдля измененияthis.
Практическое правило: обычные функции (function) использовать для методов объектов и там, где нужен динамический this (например, обработчики событий на DOM-элементах, где this должен быть самим элементом). Стрелочные — для колбэков, где нужно сохранить this внешнего контекста (например, внутри методов класса или React-компонентов).
Подробный разбор поведения this в стрелочных функциях — отдельная тема, здесь важно только то, что стрелочная функция не создаёт свой контекст, а "прозрачно" использует внешний.
Пример
// Hoisting: declaration работает до объявления
console.log(sum(2, 3)) // 5 — работает
function sum(a, b) {
return a + b
}
// Function expression: TDZ / undefined до присваивания
console.log(typeof multiply) // "undefined" (var) — переменная поднята, значение нет
var multiply = function (a, b) {
return a * b
}
// console.log(divide()) // ReferenceError: Cannot access 'divide' before initialization
const divide = (a, b) => a / b
// Собственный this у обычной функции vs лексический у стрелочной
const counter = {
value: 0,
incRegular: function () {
setTimeout(function () {
// this здесь — не counter (в строгом режиме undefined)
console.log(this === counter) // false
}, 0)
},
incArrow: function () {
setTimeout(() => {
// стрелочная берёт this из incArrow, а там this === counter
console.log(this === counter) // true
}, 0)
},
}
counter.incRegular()
counter.incArrow()
// arguments: есть у обычной функции, отсутствует у стрелочной
function regular() {
return arguments.length
}
console.log(regular(1, 2, 3)) // 3
const arrow = (...args) => args.length // так стрелочная имитирует arguments
console.log(arrow(1, 2, 3)) // 3
Дополнительные вопросы
- Почему стрелочную функцию нельзя использовать как конструктор с
new? - Что такое temporal dead zone и как она связана с хостингом
let/const? - Как метод объекта, объявленный стрелочной функцией, ломает ожидаемое поведение
this?