Объявления функций внутри операторов if/else?
Как обрабатываются объявления функций в JavaScript?
У меня возникла проблема с объявлениями функций в JavaScript, которые ведут себя по-разному в разных браузерах. Рассмотрим следующий код:
var abc = '';
if (1 === 0) {
function a() {
abc = 7;
}
} else if ('a' === 'a') {
function a() {
abc = 19;
}
} else if ('foo' === 'bar') {
function a() {
abc = 'foo';
}
}
a();
document.write(abc); // выводит "foo", хотя 'foo' !== 'bar'
При выполнении этого кода в Chrome и Firefox я получаю разные результаты: Chrome выводит foo
, тогда как Firefox выводит 19
.
Я пытаюсь понять, почему так происходит, и как именно в JavaScript обрабатываются объявления функций в ветвлении условий. Почему разные браузеры выводят разные значения? Какие правила здесь действуют?
3 ответ(ов)
В JavaScript существуют два основных способа объявления функций: декларации функций и выражения функций.
Декларация функции:
function foo() {
}
Выражение функции:
var foo = function() {
}
Как указывает ресурс Adequately Good:
“Декларации функций и переменные функции всегда перемещаются (или «поднимаются») в верхнюю часть области видимости JavaScript интерпретатором.”
Это означает, что в первом примере декларация функции function a()
поднимается в область видимости JavaScript, что позволяет использовать foo
, даже если условие if
не выполнено.
Сравните var foo
с обычным выражением JavaScript; оно выполняется только во время выполнения вашего кода, в отличие от function foo()
. Это объясняет, почему следующий код работает:
alert(foo());
function foo() {
return 'gw ganteng';
}
В данном случае function foo()
обрабатывается парсером, и foo()
попадает в текущую область видимости до того, как JavaScript пытается вызвать alert(foo())
.
Также стоит отметить, что в JavaScript есть контекст (который ECMA 5 разбивает на LexicalEnvironment, VariableEnvironment и ThisBinding) и процесс (набор операторов, которые выполняются последовательно). Декларации влияют на VariableEnvironment при входе в область выполнения. Они отличаются от операторов (таких как return) и не подчиняются их правилам.
ECMA-262 версии 5 требует, чтобы реализации регистрировали все объявления функций и переменных во время первого прохода при входе в любой новый глобальный или функциональный контекст выполнения. В данном случае Chrome технически прав, так как он просматривает блоки else
и then
, регистрируя a()
до выполнения. К сожалению, это приводит к наименее читаемым результатам.
Firefox же дожидается оценки условного оператора if
, прежде чем добавлять объявления функций и переменных в текущий контекст. Кстати, оба браузера действуют аналогично в блоках catch
и finally
.
В общем, это всего лишь вопрос двух различных реализаций ECMA, которые справляются с особенностью, которая изначально не должна была существовать. Рассматриваемый сценарий демонстрирует, почему объявления функций не следует помещать внутрь операторов управления потоком.
Объявления функций недоступны за пределами `
if (true) {
function sayHi() {
alert("hii");
}
sayHi(); // доступно
}
sayHi(); // ошибка, недоступно, так как за пределами блока
Если вы хотите определять функции условно, используйте функциональные выражения, такие как:
let sayHi;
if (true) {
sayHi = function() {
alert("hii");
}
sayHi(); // доступно
}
sayHi(); // доступно
Такое поведение связано с тем, что функции, объявленные с помощью function
, имеют подъем (hoisting) в пределах блока if
, тогда как функциональные выражения привязываются к переменной и могут быть использованы после их инициализации.
Каков объем видимости переменных в JavaScript?
Определение глобальной переменной в функции JavaScript
Установка значения по умолчанию для параметра функции в JavaScript
В чем разница между call и apply?
Передать дополнительный аргумент в функцию обратного вызова