Skip to content
Иван Ткаченко Блог
EN
var, let, const: в чём реальная разница — Иван Ткаченко ← К статьям
JavaScript

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 устарел.