Три ключевых слова, одна задача
В JavaScript переменные можно объявить тремя способами: var, let и const. Все они привязывают имя к значению, но отличаются областью видимости, правилами переприсваивания и поведением до момента самого объявления. В современном коде по умолчанию берут const, let — когда значение должно меняться, а var почти не используют.
Если совсем коротко:
Оставшаяся часть страницы объясняет, почему эти три строки ведут себя именно так.
const — выбор по умолчанию
const создаёт привязку, которую нельзя переприсвоить. Если вы написали const x = 5, то позже сказать x = 6 уже не получится — движок выбросит TypeError.
Вот и всё правило. В хорошо написанном коде большинство переменных вообще не нужно переприсваивать, так что const отлично подходит в подавляющем большинстве случаев. А когда вы по умолчанию тянетесь к const, те места, где значение действительно меняется, сразу бросаются в глаза.
Типичная точка путаницы: const защищает саму привязку, а не значение. Если за переменной стоит объект или массив, его содержимое всё равно можно менять:
const означает «эта переменная всегда ссылается на один и тот же объект». Это вовсе не значит, что «объект никогда не меняется». Если нужно заморозить сам объект — для этого есть Object.freeze(user). Но на практике большинство разработчиков просто договариваются не мутировать объекты, объявленные через const.
let: когда действительно нужно переприсваивание
let ведёт себя точно так же, как const, с одним отличием — значение можно переприсваивать. Используйте let для счётчиков, аккумуляторов, переменных цикла и вообще везде, где значение реально меняется со временем.
Если заметили, что объявили переменную через let, но ни разу её не переприсваиваете — смело меняйте на const. В большинстве проектов линтер сам подскажет это сделать.
Блочная область видимости в JavaScript
И let, и const имеют блочную область видимости. Блок — это всё, что находится между { и }: тело if, for, функции или даже просто отдельный блок { ... }. Переменная, объявленная внутри блока, снаружи уже недоступна.
Именно такое поведение вам и нужно. Переменные остаются в пределах того блока, к которому относятся, а случайные конфликты имён становятся невозможными.
С var так не получится — и это главная причина, почему от него стоит отказаться.
var: функциональная область видимости и её сюрпризы
var живёт в ближайшей функции, а не в ближайшем блоке. То есть var, объявленный внутри if или for, «утекает» наружу и становится виден во всей окружающей функции:
Именно такая «размытая» область видимости — источник длинного списка классических багов JavaScript. Самый известный пример: цикл, в котором все итерации делят одну и ту же переменную var i, и в итоге все колбэки видят её финальное значение.
Ещё var спокойно позволяет повторно объявить переменную с тем же именем в той же области видимости — и это отлично маскирует опечатки:
Современный JS-код почти всегда пишут через let и const. var встречается в легаси-проектах, в старых ответах на Stack Overflow да в скриптах, которым нужно работать в совсем древних браузерах.
Hoisting и temporal dead zone
Все три способа объявления переменных подвержены всплытию (hoisting) — движок узнаёт о них ещё до того, как начинает выполнять код внутри блока, — но ведут они себя до строки с объявлением по-разному.
var всплывает и сразу получает значение undefined. Обратиться к такой переменной можно ещё до строки с var, и ошибки не будет:
let и const тоже проходят hoisting, только без инициализации. Если обратиться к ним до объявления — получите ошибку. Этот промежуток между входом в область видимости и выполнением самого объявления называют временной мёртвой зоной (temporal dead zone, TDZ):
TDZ — это не баг, а фича. Благодаря ей ситуация «использовал до объявления» превращается из тихого undefined в явную ошибку, и это ловит кучу опечаток и проблем с порядком кода.
Переменная цикла const в for...of
Небольшой, но частый случай: цикл for...of на каждой итерации создаёт новую привязку, поэтому для переменной цикла спокойно можно использовать const — хотя формально она «меняется» от итерации к итерации.
На каждой итерации создаётся своя привязка name — никакой единой переменной, которую бы переприсваивали, тут нет. А вот классический for (let i = 0; i < n; i++) по-прежнему требует let, потому что i — это одна привязка, которая инкрементируется.
Практическое правило
Выбирайте способ объявления переменных в таком порядке приоритета:
const— по умолчанию. Если значение не будет переприсвоено, так об этом и скажите.let— когда привязке действительно нужно меняться.var— только если работаете с кодовой базой, где это требуется.
Если придерживаться этого подхода последовательно, назначение каждой переменной становится понятно с одного взгляда: можно ли её переприсвоить, ограничена ли она блоком или функцией.
MAX_RETRIES и users не меняются — значит, const. successful растёт по ходу цикла — let. user на каждой итерации создаётся заново — снова const. Пробегая глазами сверху вниз, сразу понимаешь, какие значения двигаются, а какие прибиты гвоздями, — и для этого даже не нужно запускать код.
Дальше: примитивные типы
Раз уж вы умеете объявлять переменные, логичный следующий вопрос — какие значения в них вообще можно класть. В JavaScript есть небольшой набор примитивных типов: числа, строки, булевы значения и ещё пара штук — у каждого свои особенности. Об этом поговорим на следующей странице.
Часто задаваемые вопросы
В чём разница между let, const и var?
let и const появились в ES2015 и имеют блочную область видимости, а var — старый вариант с функциональной областью видимости. Значение, объявленное через const, нельзя переприсвоить, через let — можно. Кроме того, var поднимается (hoisting) и сразу инициализируется значением undefined, тогда как let и const тоже поднимаются, но обратиться к ним до строки объявления нельзя — это и есть temporal dead zone.
Делает ли const значение неизменяемым?
Нет. const запрещает только переприсваивание самой переменной. Если там лежит объект или массив, его содержимое спокойно меняется — код const user = {}; user.name = 'Ada' отработает без ошибок. Для настоящей неизменяемости используйте Object.freeze или специализированные библиотеки вроде Immer.
Стоит ли ещё использовать var в современном JavaScript?
Практически никогда. У let и const более предсказуемая область видимости, и многие ошибки отлавливаются ещё на этапе парсинга. var вам встретится разве что в старом легаси-коде или в скриптах, которым нужно работать в окружениях без поддержки ES2015.