var, let, const: в чём реальная разница
Продолжаю серию про базовые вопросы для подготовки к собеседованиям. let и const предпочтительнее var — известный факт. Разберём почему это так и как всё это устроено.
Область видимости
Главное отличие var от let и const — область видимости.
var существует в функциональной области видимости (function scope): переменная видна во всей функции, в которой объявлена, независимо от вложенных блоков.
let и const существуют в блочной области видимости (block scope): переменная видна только внутри блока {}, в котором объявлена — будь то if, for, while или просто фигурные скобки.
if (true) {
var a = 1;
let b = 2;
}
console.log(a); // 1 — var доступна за пределами блока
console.log(b); // ReferenceError — let недоступна за пределами блока
var, объявленная на верхнем уровне скрипта (не модуля), попадает в глобальный объект window в браузере.
let и const в глобальный объект не попадают — они остаются в области видимости скрипта.
var в цикле: классическая ловушка
Разница в скоупе хорошо видна на примере с setTimeout в цикле.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 3 3 3
console.log(i); // 3 — var доступна за пределами цикла
var не создаёт новую переменную на каждую итерацию — у всего цикла одна переменная i. Цикл завершается раньше, чем срабатывает первый колбэк, к тому моменту i уже равно 3.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 0 1 2
console.log(i); // ReferenceError — let недоступна за пределами цикла
В случае с let для каждой итерации создаётся отдельная переменная i, таким образом каждый колбэк видит своё i.
Повторное объявление
var позволяет объявить переменную с тем же именем повторно в том же скоупе, и это не приведёт к ошибке.
var x = 1;
var x = 2; // работает, x = 2
let y = 1;
let y = 2; // SyntaxError: Identifier 'y' has already been declared
Также var позволяет молча перезаписать функцию с тем же именем:
function calculate() {
return 42;
}
var calculate = "oops";
calculate(); // TypeError: calculate is not a function
let и const бросают SyntaxError при попытке переобъявить переменную или функцию в том же скоупе. Это помогает поймать опечатки и случайные дубли на этапе парсинга, до выполнения кода.
const — не про иммутабельность
const в JavaScript не стоит воспринимать как классическую константу. Она запрещает переназначить переменную, но не запрещает изменять содержимое объекта или массива.
const user = { name: "Вася" };
user.name = "Петя"; // содержимое объекта можно менять
user = {}; // TypeError: Assignment to constant variable
const arr = [1, 2, 3];
arr.push(4); // массив можно изменять
arr = []; // TypeError
const гарантирует, что переменная всегда указывает на один и тот же объект. Что происходит внутри этого объекта — уже не её дело. Она защищает ссылку, но не объект.
catch(e) — первый block scope в истории
До появления let и const в ES6 единственным block scope в JavaScript был блок catch.
try {
throw new Error("упс");
} catch (e) {
console.log(e.message); // 'упс'
}
console.log(e); // ReferenceError: e is not defined
Переменная e видна только внутри блока catch. За его пределами её не существует — ещё до того, как в языке появился let.
Выглядит как баг, ставший фичей. Транспайлеры вроде Babel этим пользовались — компилировали let в try/catch, чтобы эмулировать block scope в ES5.
Что использовать в современном коде
var в современном коде лучше не использовать — function scope и молчаливое переопределение легко порождают баги, которые сложно отловить.
Вместо var следует использовать const во всех случаях, когда не требуется переназначения, иначе let.
// когда значение постоянное, и мы не планируем его менять
const MAX_SIZE = 100;
// объект можно изменять, но присвоить другое значение переменной user не получится
const user = { name: "Вася" };
// когда очевидно, что значение переменной будет меняться, используем let
let count = 0;
count++;
Если хочется защитить объект от мутаций — Object.freeze(), но это уже отдельная история.
Почему это спрашивают на собеседовании
var, let, const — это не просто синтаксический выбор. За каждым стоит разная механика области видимости и объявлений.
- function scope vs block scope.
varв цикле илиifведёт себя не так, как можно ожидать. Понимание этой разницы объясняет целый класс багов. - Переопределение
var. Молчаливое переопределение переменной с тем же именем — источник трудноуловимых ошибок.letиconstделают это невозможным. constи объекты. Распространённое заблуждение:constозначает неизменяемость. На самом делеconstследит только за тем, чтобы переменная не указала на что-то другое — объект внутри мутировать можно как угодно.- TDZ.
letиconstне инициализируются до строки с объявлением — об этом подробнее в статье про хойстинг.
На собеседовании этот вопрос проверяет, понимаете ли вы как JavaScript управляет переменными, а не просто знаете ли что var устарел.
← К статьям