Почему этот вопрос мучает даже опытных разработчиков?
Если вы хоть раз писали на JavaScript, то наверняка встречали оба этих способа объявления функций. Они выглядят похоже, но ведут себя совершенно по-разному. На Stack Overflow этот вопрос собрал тысячи просмотров и десятки страниц обсуждений. Почему? Потому что непонимание разницы между var functionName = function() {} и function functionName() {} приводит к багам, которые сложно отловить.
Сегодня мы раз и навсегда разберемся в этой теме. Вы узнаете, что такое function declaration и function expression, как работает hoisting, и когда какой способ использовать. Поехали!
Что скрывается за синтаксисом?
Начнем с базы. Первый вариант — это Function Declaration (объявление функции):
function greet() {
console.log('Привет!');
}
Второй — Function Expression (функциональное выражение):
const greet = function() {
console.log('Привет!');
};
Обратите внимание: в первом случае функция имеет имя и стоит в начале строки. Во втором — мы присваиваем анонимную функцию переменной greet. Но самое интересное начинается, когда мы смотрим, как JavaScript обрабатывает эти объявления.
Главное отличие: Hoisting (поднятие)
Hoisting — это механизм JavaScript, при котором объявления переменных и функций перемещаются вверх своей области видимости перед выполнением кода. Но работает он по-разному для declaration и expression.
Function Declaration поднимается целиком
Когда вы используете function functionName() {}, JavaScript «поднимает» всё объявление функции вверх. Это значит, что вы можете вызвать функцию до того, как она объявлена в коде:
sayHello(); // "Привет!"
function sayHello() {
console.log('Привет!');
}
Код выполнится без ошибок. Это удобно, но может сбивать с толку новичков.
Function Expression не поднимается
С var functionName = function() {} история другая. Поднимается только объявление переменной, но не её значение. Функция остаётся на месте:
sayHello(); // TypeError: sayHello is not a function
var sayHello = function() {
console.log('Привет!');
};
Почему так? Потому что var поднимает объявление переменной sayHello (со значением undefined), а присваивание функции происходит только на строке с выражением. Поэтому до этой строки sayHello — это undefined, и попытка вызвать его как функцию приводит к ошибке.
Кстати, если заменить var на let или const, ошибка будет другой — ReferenceError: Cannot access 'sayHello' before initialization. Это связано с тем, что let и const не инициализируются до момента объявления (так называемая «временная мёртвая зона»).
Область видимости: ещё один камень преткновения
И function declaration, и function expression подчиняются правилам области видимости. Но есть нюанс.
Function Declaration видна в пределах блока
Хотя function declaration поднимается, её область видимости ограничена блоком (в строгом режиме):
'use strict';
if (true) {
function greet() {
console.log('Внутри блока');
}
greet(); // "Внутри блока"
}
greet(); // ReferenceError: greet is not defined (в строгом режиме)
В нестрогом режиме такое объявление «вылезет» наружу, но полагаться на это не стоит.
Function Expression живёт в своей переменной
С function expression всё предсказуемо: функция доступна только там, где доступна переменная. Если переменная объявлена внутри блока, то и функция будет только там:
if (true) {
const greet = function() {
console.log('Внутри блока');
};
greet(); // "Внутри блока"
}
greet(); // ReferenceError: greet is not defined
Это делает function expression более безопасным и предсказуемым выбором для современного кода.
Именованные Function Expression: тёмная лошадка
Вы знали, что у function expression может быть имя? Да, это не обязательно анонимная функция:
const greet = function sayHello() {
console.log('Привет!');
};
Такая конструкция называется Named Function Expression. Имя sayHello доступно только внутри самой функции (например, для рекурсии) и не загрязняет внешнюю область видимости:
const factorial = function calc(n) {
if (n <= 1) return 1;
return n * calc(n - 1); // используем имя для рекурсии
};
console.log(factorial(5)); // 120
console.log(calc); // ReferenceError: calc is not defined
Это удобно для отладки: в стеке вызовов будет отображаться имя функции, а не (anonymous).
Когда что использовать? Практические советы
Теперь, когда вы знаете разницу, давайте разберём, какой способ выбрать в реальном проекте.
Когда использовать Function Declaration
- Глобальные функции-утилиты, которые нужны во всём скрипте. Например,
formatDate()илиvalidateEmail(). - Когда важна читаемость и вы хотите, чтобы функция была доступна до её объявления (например, в рекурсивных алгоритмах, где функция вызывает саму себя).
- Коллбэки в старом коде, где ещё используется
var.
Когда использовать Function Expression
- Коллбэки и обработчики событий:
button.addEventListener('click', function() { ... }). - Современный код на ES6+: используйте
constилиletдля объявления функций. Это делает код более предсказуемым. - Условное объявление функций: например, вы хотите определить разную реализацию в зависимости от условий:
let calculate;
if (typeof window !== 'undefined') {
calculate = function() {
// браузерная версия
};
} else {
calculate = function() {
// серверная версия
};
}
- Методы объектов (хотя для этого есть синтаксический сахар —
method() {}).
Сравнительная таблица: наглядное резюме
Чтобы закрепить материал, вот краткая таблица различий:
| Характеристика | Function Declaration | Function Expression |
|---|---|---|
| Синтаксис | function name() {} |
const name = function() {}
|