let vs var в JavaScript: что выбрать для скорости и кода?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

let vs var: война миров или эволюция JavaScript?

Вы когда-нибудь писали код на JavaScript и ловили себя на мысли: «А почему эта переменная ведёт себя так странно?» Или, что ещё хуже, натыкались на баг, который исчезает при замене var на let? Добро пожаловать в клуб. Вопрос «What is the difference between "let" and "var"?» на Stack Overflow собирает сотни тысяч просмотров не просто так — это одна из самых частых ловушек для новичков и даже опытных разработчиков.

Сегодня мы не просто перечислим различия. Мы заглянем под капот: разберёмся, как let и var влияют на производительность вашего кода, оптимизацию в современных движках (V8, SpiderMonkey) и почему выбор между ними — это вопрос не только стиля, но и эффективности. Приготовьтесь, будет жарко.

Коротко о главном: область видимости и поднятие (hoisting)

Прежде чем говорить о производительности, давайте вспомним фундамент. У var и let три ключевых отличия, которые меняют всё.

1. Область видимости (Scope)

  • var — имеет функциональную область видимости. Если переменная объявлена внутри функции, она видна во всей функции. Если вне — она глобальна.
  • let — имеет блочную область видимости. Она живёт только внутри ближайших фигурных скобок {} (цикл, условие, блок).
// Пример с var
if (true) {
    var x = 10;
}
console.log(x); // 10 — переменная "утекла" из блока

// Пример с let
if (true) {
    let y = 20;
}
console.log(y); // ReferenceError: y is not defined

2. Hoisting (поднятие) и Temporal Dead Zone (TDZ)

Обе переменные «поднимаются» (hoisting), но ведут себя по-разному.

  • var: объявление поднимается наверх области видимости и инициализируется значением undefined. Вы можете обратиться к переменной до её объявления (но получите undefined).
  • let: объявление поднимается, но не инициализируется. До момента фактического объявления переменная находится в «временной мёртвой зоне» (Temporal Dead Zone). Любое обращение к ней вызывает ReferenceError.
// var — "безопасное" undefined
console.log(a); // undefined
var a = 5;

// let — ошибка в TDZ
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;

3. Повторное объявление

  • var — можно переобъявить сколько угодно раз в одной области видимости. Ошибки не будет.
  • let — повторное объявление в той же области видимости вызовет SyntaxError.
var z = 1;
var z = 2; // Ок

let w = 1;
let w = 2; // SyntaxError: Identifier 'w' has already been declared

А теперь о главном: производительность и оптимизация

Многие думают: «Раз let — это новый стандарт, значит он медленнее var?» Или наоборот: «let круче, значит быстрее». Правда, как всегда, посередине и зависит от контекста.

Как движки оптимизируют переменные

Современные JavaScript-движки (V8 в Chrome/Node.js, SpiderMonkey в Firefox) компилируют код в машинный с помощью JIT-компиляторов. Они используют эвристики для оптимизации.

  • var: из-за функциональной области видимости и возможности случайного переопределения движку сложнее делать предположения о типе переменной. Он часто создаёт «тяжёлые» объекты VariableObject, которые хранят переменные в динамической памяти.
  • let: блочная область видимости и запрет на повторное объявление дают движку больше информации. Он может разместить переменную в регистре процессора или на стеке, а не в куче. Это ускоряет доступ.

Однако на практике разница в производительности для 99% кода микроскопическая. Вы не заметите её, пока не начнёте выполнять миллиарды операций.

Где разница становится заметной: циклы

Классический пример — производительность в циклах for. Из-за разной области видимости, let в заголовке цикла создаёт новую привязку для каждой итерации. Это критично для замыканий.

// Проблема с var
for (var i = 0; i < 5; i++) {
    setTimeout(() => console.log(i), 100); // Выведет: 5,5,5,5,5
}

// Решение с let
for (let j = 0; j < 5; j++) {
    setTimeout(() => console.log(j), 100); // Выведет: 0,1,2,3,4
}

С точки зрения производительности, let в цикле может быть чуть медленнее из-за создания новой лексической среды на каждой итерации. Но это окупается предсказуемостью и отсутствием багов. В современных движках эта разница нивелируется оптимизациями.

Память и сборка мусора

let позволяет более точно контролировать время жизни переменной. Переменная, объявленная внутри блока, перестаёт существовать после выхода из него. Это помогает сборщику мусора быстрее освобождать память.

// var — живёт до конца функции
function process() {
    if (condition) {
        var bigData = new Array(1000000);
        // ... работаем
    }
    // bigData всё ещё существует здесь, занимает память
}

// let — умирает после блока
function processOptimized() {
    if (condition) {
        let bigData = new Array(1000000);
        // ... работаем
    }
    // bigData уже уничтожена, память свободна
}

В больших приложениях (React, Angular, серверный Node.js) такая экономия памяти может дать реальный прирост производительности и уменьшить фризы от сборки мусора.

Практические советы: когда что использовать?

На основе анализа производительности и лучших практик, вот простой гайд:

  • Всегда используйте const для переменных, которые не переназначаются. Это даёт движку максимальную информацию для оптимизации.
  • Используйте let, если значение переменной будет меняться (счётчики, флаги). Это безопаснее и понятнее.
  • Избегайте var в новом коде. Исключение — если вы поддерживаете очень старый код (IE11 и ниже) или пишете скрип

Блоги

Рекомендаций по книгам