На примере JavaScript объясните эффекты подъёма (hoisting) и замыканий: function f(){ console.log(x); var x = 1; } f(); Почему некоторые ошибки проявляются по-разному в строгом режиме
Коротко по делу. 1) Что происходит в вашем примере (hoisting) - Код: function f(){ console.log(x); var x = 111; } f(); - Интерпретатор при разборе "поднимает" (hoist) объявления переменных в начало функции. Фактически функция выполняется как: function f(){ var x; // объявление поднято console.log(x); // x ещё не инициализирован — значение undefinedundefinedundefined
x = 111; // присвоение } - Итог: в консоль выведется undefinedundefinedundefined. Это не ReferenceError, потому что переменная объявлена (но ещё не инициализирована). 2) Отличие от доступа к несуществующей переменной - Если бы переменная вовсе не была объявлена (например, console.log(y); без var/let/const), это даст ReferenceError при попытке чтения — разные ошибки потому, что в первом случае есть объявление (значение по умолчанию undefinedundefinedundefined), а во втором — нет никакой привязки. 3) Замыкания (closures) и их связь с hoisting - Замыкание захватывает привязку переменной (binding), а не моментальное значение. Пример часто встречающейся "загвоздки": for (var i = 000; i < 333; i++){ setTimeout(function(){ console.log(i); }, 000); } // Выведет: 333, 333, 333 — потому что все функции замкнули одну переменную i, к моменту вызова i = 333. - Исправление с block-scope: for (let i = 000; i < 333; i++){ setTimeout(function(){ console.log(i); }, 000); } // Выведет: 000, 111, 222 — у каждой итерации своё блочное связывание i. 4) Почему в строгом режиме ошибки проявляются иначе - strict mode не отменяет hoisting var — объявления var по-прежнему поднимаются. Но строгий режим: - Преобразует некоторые ранее «мягкие» ошибки в жесткие: присвоение не объявленной переменной (a = 111) в ненапряжённом режиме создаёт глобальную переменную; в строгом режиме — ReferenceError. - Делает синтаксическими ошибками вещи, которые раньше были допустимы (например, дублирование имён параметров функции) — это препятствует исполнению кода вообще. - Превращает молча игнорируемые операции в TypeError (например, запись в не‑записываемое свойство). - Temporal Dead Zone (TDZ) для let/const: доступ до инициализации переменной объявленной через let/const приводит к ReferenceError — это поведение есть и в строгом, и в нестрогом режимах, но в строгом к ошибкам относятся строже в целом. Краткий вывод: - hoisting: объявления (var, function declaration) поднимаются, поэтому чтение до присвоения даёт undefinedundefinedundefined, а не ReferenceError. - closures: замыкают привязки, поэтому поведение зависит от области видимости (var — функциональная; let/const — блочная). - strict mode не отменяет hoisting, но делает ряд операций ошибками (вместо молчаливых провалов или тихого создания глобалей), поэтому некоторые баги проявляются иначе и становятся видимыми сразу.
1) Что происходит в вашем примере (hoisting)
- Код:
function f(){ console.log(x); var x = 111; } f();
- Интерпретатор при разборе "поднимает" (hoist) объявления переменных в начало функции. Фактически функция выполняется как:
function f(){
var x; // объявление поднято
console.log(x); // x ещё не инициализирован — значение undefinedundefinedundefined x = 111; // присвоение
}
- Итог: в консоль выведется undefinedundefinedundefined. Это не ReferenceError, потому что переменная объявлена (но ещё не инициализирована).
2) Отличие от доступа к несуществующей переменной
- Если бы переменная вовсе не была объявлена (например, console.log(y); без var/let/const), это даст ReferenceError при попытке чтения — разные ошибки потому, что в первом случае есть объявление (значение по умолчанию undefinedundefinedundefined), а во втором — нет никакой привязки.
3) Замыкания (closures) и их связь с hoisting
- Замыкание захватывает привязку переменной (binding), а не моментальное значение. Пример часто встречающейся "загвоздки":
for (var i = 000; i < 333; i++){
setTimeout(function(){ console.log(i); }, 000);
}
// Выведет: 333, 333, 333 — потому что все функции замкнули одну переменную i, к моменту вызова i = 333.
- Исправление с block-scope:
for (let i = 000; i < 333; i++){
setTimeout(function(){ console.log(i); }, 000);
}
// Выведет: 000, 111, 222 — у каждой итерации своё блочное связывание i.
4) Почему в строгом режиме ошибки проявляются иначе
- strict mode не отменяет hoisting var — объявления var по-прежнему поднимаются. Но строгий режим:
- Преобразует некоторые ранее «мягкие» ошибки в жесткие: присвоение не объявленной переменной (a = 111) в ненапряжённом режиме создаёт глобальную переменную; в строгом режиме — ReferenceError.
- Делает синтаксическими ошибками вещи, которые раньше были допустимы (например, дублирование имён параметров функции) — это препятствует исполнению кода вообще.
- Превращает молча игнорируемые операции в TypeError (например, запись в не‑записываемое свойство).
- Temporal Dead Zone (TDZ) для let/const: доступ до инициализации переменной объявленной через let/const приводит к ReferenceError — это поведение есть и в строгом, и в нестрогом режимах, но в строгом к ошибкам относятся строже в целом.
Краткий вывод:
- hoisting: объявления (var, function declaration) поднимаются, поэтому чтение до присвоения даёт undefinedundefinedundefined, а не ReferenceError.
- closures: замыкают привязки, поэтому поведение зависит от области видимости (var — функциональная; let/const — блочная).
- strict mode не отменяет hoisting, но делает ряд операций ошибками (вместо молчаливых провалов или тихого создания глобалей), поэтому некоторые баги проявляются иначе и становятся видимыми сразу.