let и const в JavaScript объявляют переменные с блочной областью видимости. Разница одна: переменной let можно присвоить новое значение, переменной const нельзя, и ей сразу при объявлении нужно дать начальное значение.
Оба ключевых слова появились в ECMAScript 2015 (ES6), и актуальные браузеры поддерживают их полностью. До них переменные создавали только через var. Все примеры запускались в Node.js 24.14, вывод скопирован из консоли и дан комментариями в конце каждого примера. В Chrome тот же движок V8, поэтому тексты ошибок там совпадают; Firefox формулирует их иначе.
- Отличие let, const и var в одной таблице
- Объявление переменных в JavaScript
- Переменные let: значение можно менять
- Константы const: переприсвоить нельзя
- const с объектами и массивами
- Блочная область видимости
- Поднятие (hoisting) и временная мёртвая зона
- let в цикле for и замыкания
- Почему var устарел и где он ещё встречается
- Что выбрать: let или const
- Имена переменных
- Частые ошибки и что они означают
- Частые вопросы
- Можно ли объявить несколько переменных в одной строке?
- Можно ли изменить const внутри блока?
- Поднимаются ли let и const?
- Почему в консоли Chrome можно дважды написать let с одним именем?
- Работают ли let и const в старых браузерах?
Отличие let, const и var в одной таблице
| Что проверяем | let | const | var |
|---|---|---|---|
| Область видимости | блок { } | блок { } | функция или весь скрипт |
Новое значение через = | можно | TypeError | можно |
| Объявить без значения | можно, будет undefined | SyntaxError | можно, будет undefined |
| Повторное объявление в той же области | SyntaxError | SyntaxError | можно |
| Обращение до строки объявления | ReferenceError | ReferenceError | undefined |
Свойство globalThis в обычном скрипте | нет | нет | да |
Отдельная переменная на каждой итерации for | да | только в for...of и for...in | нет |
Объявление переменных в JavaScript
Переменная хранит значение под именем, по которому к нему потом обращаются. Объявить переменную можно тремя способами: с помощью ключевого слова let, const или устаревшего var. Объявление создаёт переменную, а инициализация записывает в неё первое значение через оператор присваивания =. Обычно оба действия делают в одной строке:
let age = 25;
const name = "Аня";
let isAdmin = false;
let result = null;
console.log(typeof age, typeof name, typeof isAdmin, typeof result);
age = "двадцать шесть";
console.log(typeof age);
// number string boolean object
// string
Ни let, ни const не задают тип данных. Тип определяет значение, поэтому переменная age сначала хранила число (number), а после присваивания стала строкой (string). typeof null возвращает "object": так сложилось с первых версий языка, и ради совместимости это не исправляют.
Переменные let: значение можно менять
let подходит для счётчиков, накопителей и всего, что по ходу работы программы получает новое значение. Если объявить переменную без значения, в ней будет undefined:
let score;
console.log(score);
score = 10;
score = score + 5;
console.log(score);
// undefined
// 15
Переменная живёт внутри своего блока, то есть между ближайшими фигурными скобками: тело if, цикла, функции или просто пара { }.
Константы const: переприсвоить нельзя
Объявление const создаёт константу: переменная навсегда связана с тем значением, которое ей дали в момент создания. Попытка переприсвоить её роняет программу:
const MAX_USERS = 100;
console.log(MAX_USERS);
MAX_USERS = 200;
100
...\02-const.js:4
MAX_USERS = 200;
^
TypeError: Assignment to constant variable.
at Object.<anonymous> (...\02-const.js:4:11)
Node.js v24.14.1
Путь к файлу и стек вызовов здесь сокращены. Число 100 успело вывестись: ошибка TypeError возникает во время выполнения, когда программа дошла до присваивания. По спецификации запись в const бросает это исключение и в строгом режиме, и без него.
Объявить константу «на потом» тоже не выйдет. Такой код не запустится вообще:
const limit;
// SyntaxError: Missing initializer in const declaration
const с объектами и массивами
const запрещает переприсвоить переменную, но не делает объект неизменяемым. Константа хранит ссылку на объект, и эта ссылка не меняется. Свойства объекта и элементы массива менять можно:
const user = { name: "Аня", tags: ["frontend"] };
user.name = "Оля";
user.tags.push("backend");
console.log(user);
try {
user = { name: "Ира" };
} catch (e) {
console.log(e.name + ": " + e.message);
}
// { name: 'Оля', tags: [ 'frontend', 'backend' ] }
// TypeError: Assignment to constant variable.
Изменение свойства user.name и вызов push прошли. Запись нового объекта в user закончилась ошибкой. Если объект нужно запретить менять, используют Object.freeze(). Но заморозка поверхностная, и в нестрогом режиме нарушение проходит молча:
const config = Object.freeze({ port: 3000, db: { host: "localhost" } });
config.port = 8080;
config.db.host = "example.com";
console.log(config.port, config.db.host);
// Результат в файле .js (CommonJS, нестрогий режим):
// 3000 example.com
Значение port осталось прежним, а вложенный объект db изменился: freeze его не тронул. Тот же код в файле .mjs или с директивой "use strict" в начале падает на первой же записи:
TypeError: Cannot assign to read only property 'port' of object '#<Object>'
ES-модули всегда работают в строгом режиме, поэтому в проектах на import вы увидите именно эту ошибку, а в старом скрипте без "use strict" запись просто потеряется. Чтобы заморозить вложенные объекты, Object.freeze вызывают для каждого уровня.
Блочная область видимости
Область видимости определяет, из какой части кода можно обратиться к переменной, а где доступа к ней нет. У let и const это блок, у var функция. Хорошо видно на if:
if (true) {
var a = "var";
let b = "let";
const c = "const";
}
console.log(a);
console.log(typeof b, typeof c);
console.log(b);
// var
// undefined undefined
// ReferenceError: b is not defined
Переменная a вытекла из блока и доступна ниже. Переменные b и c за пределами фигурных скобок не существуют. typeof для необъявленной переменной возвращает "undefined", а прямое обращение к b даёт ReferenceError.
Блочная область видимости защищает от случайной перезаписи: let i во вложенном цикле не испортит i внешнего цикла, а временная переменная внутри if не заденет одноимённую снаружи.
Поднятие (hoisting) и временная мёртвая зона
Поднятие означает, что движок узнаёт обо всех объявлениях блока или функции до того, как начнёт выполнять код. С var переменная при этом сразу получает значение undefined. С let и const переменная тоже создаётся заранее, но остаётся неинициализированной до строки объявления. Отрезок от начала блока до этой строки называют временной мёртвой зоной (temporal dead zone, TDZ):
console.log(oldStyle);
var oldStyle = 1;
console.log(modern);
let modern = 2;
// undefined
// ReferenceError: Cannot access 'modern' before initialization
Часто пишут, что let и const не поднимаются. Мы считаем эту формулировку неточной:
let price = 100;
{
console.log(price);
let price = 200;
}
// ReferenceError: Cannot access 'price' before initialization
Если бы внутренний price не существовал до своего объявления, console.log взял бы внешнюю переменную и вывел 100. Но внутренний price уже создан, просто не инициализирован, и читать его нельзя.
С typeof то же самое. Для переменной, которой нигде нет, он безопасен, а для переменной в мёртвой зоне падает:
console.log(typeof notDeclared);
{
console.log(typeof later);
let later = 1;
}
// undefined
// ReferenceError: Cannot access 'later' before initialization
Зона называется временной, потому что считается по порядку выполнения кода. Функция может ссылаться на константу, объявленную ниже, если её вызывают после объявления:
function showTotal() {
console.log(total);
}
const total = 42;
showTotal();
// 42
Вызов showTotal() до объявления const total дал бы ту же ошибку Cannot access 'total' before initialization.
let в цикле for и замыкания
Колбэк setTimeout выполняется после того, как цикл закончился:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 0);
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log("let:", j), 0);
}
// var: 3
// var: 3
// var: 3
// let: 0
// let: 1
// let: 2
С var на весь цикл одна переменная i, и к моменту вызова колбэков её значение уже 3. С let в заголовке for JavaScript создаёт новую переменную на каждой итерации и копирует в неё текущее значение, поэтому каждая стрелочная функция запоминает своё число.
const в заголовке работает в for...of и for...in: там на каждом шаге создаётся новая константа. В классическом for счётчик меняется через k++, и это присваивание:
for (const item of ["a", "b", "c"]) {
console.log(item);
}
for (const k = 0; k < 3; k++) {
console.log(k);
}
// a
// b
// c
// 0
// TypeError: Assignment to constant variable.
Первая итерация прошла и напечатала 0, ошибка возникла на k++. Для счётчика в обычном for нужен let.
Почему var устарел и где он ещё встречается
var нужен, чтобы читать старый код и библиотеки, написанные до 2015 года. Три свойства делают его источником ошибок: функциональная область видимости (переменная вытекает из if и циклов), undefined вместо ошибки при обращении до объявления и тихое повторное объявление.
var x = 1;
var x = 2;
console.log(x);
let y = 1;
let y = 2;
// SyntaxError: Identifier 'y' has already been declared
Здесь console.log(x) не вывел даже 2. Повторное let ловится при разборе файла, до выполнения первой команды, и не запускается ничего. Для var повтор разрешён, и в длинном файле так легко затереть чужую переменную.
Поведение var на верхнем уровне зависит от того, где запущен код. В обычном скрипте браузера (тег <script> без type="module") переменная var на верхнем уровне становится свойством глобального объекта, а let и const нет. В файле Node.js и в модулях верхний уровень принадлежит модулю, и глобальным свойством не становится никто:
var a = 1;
let b = 2;
console.log(globalThis.a, globalThis.b);
// node file.js -> undefined undefined
// node -e "..." -> 1 undefined
Режим node -e выполняет код как обычный скрипт, поэтому здесь ведёт себя как браузер.
Что выбрать: let или const
Наше правило короткое: объявляйте через const, а на let переходите, когда переменной действительно нужно присвоить новое значение. var в новом коде не используйте.
const по умолчанию сообщает читателю, что переменная дальше не переназначается, и ему не нужно просматривать функцию до конца. Если кто-то позже попробует переприсвоить такую переменную, программа упадёт с TypeError и укажет на место ошибки. Линтер ESLint умеет следить за этим автоматически: правило prefer-const находит let, которые ни разу не переприсвоены, и исправляет их командой --fix, а no-var запрещает var.
Где без let не обойтись:
- счётчик классического цикла
for; - сумма или строка, которую накапливают в цикле;
- переменная, значение которой выбирают в
ifилиtryи используют ниже; - флаг, который меняется по ходу работы, например
let isLoading = true.
Имена переменных
Имя может состоять из букв, цифр, _ и $, но не начинаться с цифры. Регистр важен: user и User это разные переменные. Зарезервированные слова вроде const, class или return именами быть не могут. Пишут в стиле camelCase: userName, totalPrice.
Верхний регистр с подчёркиванием (MAX_USERS, API_URL) принят для значений, известных до запуска программы. Мы бы не советовали писать так каждую const: const user = await getUser() остаётся обычной переменной, просто без переприсваивания, и const USER здесь только сбивает с толку. Как работает сам await, разобрано в статье про async/await в JavaScript.
Частые ошибки и что они означают
| Сообщение в консоли | Причина | Как исправить |
|---|---|---|
TypeError: Assignment to constant variable. | присваивание переменной const, в том числе ++ и += | объявить через let или завести новую переменную |
SyntaxError: Missing initializer in const declaration | const без значения | дать значение в той же строке |
ReferenceError: Cannot access 'x' before initialization | обращение к let или const до объявления, в том числе через typeof | перенести объявление выше или вызов функции ниже |
SyntaxError: Identifier 'x' has already been declared | в этой области уже есть такое имя, и одно из объявлений сделано через let, const или class | переименовать или убрать второе объявление |
ReferenceError: x is not defined | переменная объявлена в другом блоке или не объявлена совсем | объявить её там, где к ней обращаются, или во внешнем блоке |
TypeError: Cannot assign to read only property | запись в замороженный объект в строгом режиме | создать копию: { ...config, port: 8080 } |
Отдельная ловушка ждёт в switch. Все ветки case лежат в одном блоке, поэтому переменные let или const с одинаковым именем в двух ветках дают SyntaxError: Identifier 'message' has already been declared. Каждую ветку оборачивают в свои фигурные скобки:
const command = "start";
switch (command) {
case "start": {
const message = "Запуск";
console.log(message);
break;
}
case "stop": {
const message = "Остановка";
console.log(message);
break;
}
}
// Запуск

Частые вопросы
Можно ли объявить несколько переменных в одной строке?
Можно, через запятую: let a = 1, b, c = 3; создаёт три переменные, и значение b будет undefined. С const значение нужно каждой: строка const X = 10, Y; не запустится с SyntaxError: Missing initializer in const declaration. Мы бы советовали по одному объявлению на строку, так проще читать дифф в Git.
Можно ли изменить const внутри блока?
Переприсвоить константу нельзя нигде. Но внутри вложенного блока можно объявить новую константу с тем же именем, она закроет внешнюю до конца блока. Если в const лежит объект или массив, его содержимое меняется свободно.
Поднимаются ли let и const?
Да, такие переменные создаются при входе в блок. Но до объявления они не инициализированы, и любое обращение к ним даёт ReferenceError. У var в этот момент уже есть значение undefined.
Почему в консоли Chrome можно дважды написать let с одним именем?
Консоль DevTools с версии Chrome 80 специально разрешает повторное let и class, если объявления введены отдельными командами: так удобнее экспериментировать. В файле скрипта и внутри одной команды консоли повторное объявление по-прежнему даёт SyntaxError.
Работают ли let и const в старых браузерах?
Актуальные версии Chrome, Firefox, Safari и Edge поддерживают их полностью, Internet Explorer 11 только частично. Если проект обязан работать в IE, код переводят в синтаксис ES5 сборщиком, например Babel, и let с const превращаются в var.








