Призрак прошлого: Почему var остался в истории, а вы должны его забыть
Если вы откроете любой учебник JavaScript десятилетней давности, вы увидите его. Он везде: в циклах, в функциях, в глобальном пространстве. var был королём переменных. Но времена изменились.
В 2015 году в мире JavaScript произошла революция. Появились let и const, и старый добрый var внезапно стал... опасным. Не потому, что он сломался. Он работает как работал. Просто мы поняли, что его поведение - это источник багов, а не фича.
Сегодня мы разберём, почему var - это анахронизм, какие проблемы он создаёт и почему вы должны использовать let и const всегда. И, конечно, посмотрим на legacy-код, где var всё ещё жив, чтобы понимать, что там происходит.
Часть 1. Что такое var? Краткий экскурс в историю
var - это способ объявления переменных в JavaScript с момента создания языка в 1995 году. Он был единственным вариантом до появления ES6.
javascript
var name = "Анна";
var age = 25;
var isActive = true;
Всё просто. Но за этой простотой скрываются странности.
Часть 2. Проблема №1: Отсутствие блочной области видимости
Самая большая проблема var: он не знает про блоки { }. Для него существует только функциональная область видимости.
javascript
if (true) {
var message = "Привет";
}
console.log(message); // "Привет" (вылезло из блока!)
Почему это плохо? Переменная, которую вы создали внутри условия или цикла, "вытекает" наружу. Это приводит к неожиданным багам и конфликтам имён.
javascript
for (var i = 0; i < 3; i++) {
// i видна не только внутри цикла
}
console.log(i); // 3 (доступна после цикла!)
Сравнение с let:
javascript
if (true) {
let message = "Привет";
}
console.log(message); // ReferenceError: message is not defined
Часть 3. Проблема №2: Hoisting (поднятие) с undefined
Переменные, объявленные через var, "поднимаются" в начало своей области видимости. Но не их значение, а само объявление.
javascript
console.log(name); // undefined (не ошибка!)
var name = "Анна";
Как это видит движок:
javascript
var name; // объявление поднято
console.log(name); // undefined
name = "Анна"; // инициализация осталась на месте
Почему это плохо? Вы можете использовать переменную до её объявления, получить undefined и гадать, почему код не работает. В больших функциях это особенно коварно.
Сравнение с let (Temporal Dead Zone):
javascript
console.log(name); // ReferenceError: Cannot access 'name' before initialization
let name = "Анна";
let и const тоже поднимаются, но не инициализируются. До строки объявления переменная существует, но находится в "временной мёртвой зоне" (Temporal Dead Zone, TDZ). Любое обращение к ней вызывает ошибку. Это хорошо - ошибка лучше, чем undefined.
Часть 4. Проблема №3: Повторное объявление
var позволяет объявить одну и ту же переменную несколько раз. Без ошибок. Без предупреждений.
javascript
var user = "Анна";
var user = "Борис"; // перезаписали
var user = "Вика"; // и ещё раз
console.log(user); // "Вика"
Почему это плохо? Вы случайно можете перезаписать существующую переменную и даже не заметить этого. Особенно опасно в больших проектах.
Сравнение с let и const:
javascript
let user = "Анна";
let user = "Борис"; // SyntaxError: Identifier 'user' has already been declared
Часть 5. Проблема №4: Глобальные переменные становятся свойствами window
В браузере глобальные переменные, объявленные через var, становятся свойствами глобального объекта window.
javascript
var globalVar = "я глобальный";
console.log(window.globalVar); // "я глобальный"
Почему это плохо? Вы можете случайно перезаписать существующее свойство window. Например, назвать переменную name или document.
javascript
var name = "Анна"; // перезаписали window.name!
console.log(window.name); // "Анна" (а должна быть информация об окне)
let и const не добавляют свойства в window:
javascript
let globalLet = "я глобальный";
console.log(window.globalLet); // undefined
Часть 6. Проблема №5: Замыкания в циклах (классический баг)
Из-за функциональной области видимости var и отсутствия блочной области возникает классическая ошибка с замыканиями.
javascript
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3 (все три!)
}, 100);
}
Что произошло? Все три функции замыкаются на одну и ту же переменную i. К моменту вызова таймеров цикл уже завершился, i = 3.
С let всё работает правильно:
javascript
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}
let создаёт новую переменную для каждой итерации цикла.
Часть 7. Исторический контекст: почему var такой странный?
JavaScript был создан за 10 дней. У Брендана Эйха не было времени на продумывание тонкостей. Он взял идеи из разных языков:
- Функциональная область видимости - как в C (где блоки не создают новую область)
- Hoisting - особенность реализации, которая стала спецификацией
- Отсутствие блочной области - потому что в 1995 году это казалось ненужным
В 2015 году, с выходом ES6, ошибки прошлого исправили, добавив let и const. Но var оставили для обратной совместимости. Сломанный код 90-х годов всё ещё работает.
Часть 8. var в строгом режиме ("use strict")
Строгий режим не исправляет основные проблемы var, но добавляет некоторые ограничения.
javascript
"use strict";
// Всё ещё работает (к сожалению)
var x = 10;
var x = 20; // повторное объявление всё ещё разрешено
// Но это больше не создаёт глобальную переменную
function test() {
mistyped = 5; // ReferenceError (раньше создало бы window.mistyped)
}
Часть 9. Когда var всё ещё может быть полезен? (спойлер: почти никогда)
Сценарий 1: Поддержка очень старых браузеров (IE10 и ниже)
Если вам нужно поддерживать Internet Explorer 10 (который не поддерживает let и const), у вас нет выбора. Но таких проекстов становится всё меньше.
Сценарий 2: Консоль браузера (для быстрых экспериментов)
В консоли браузера var иногда удобнее, потому что переменные, объявленные через let в консоли, ведут себя странно при повторном вводе. Но это крайне специфичный случай.
Сценарий 3: Чтение старого кода
Вам нужно понимать var, потому что вы будете встречать его в legacy-коде. Но не использовать.
Часть 10. Антипаттерны с var, которые нужно знать
10.1 Случайное создание глобальной переменной
javascript
function oops() {
// забыли var/let/const
accidentalGlobal = "я стал глобальным";
}
oops();
console.log(window.accidentalGlobal); // "я стал глобальным"
10.2 Переменная, переопределённая до объявления
javascript
function confusing() {
console.log(x); // undefined (не ошибка!)
var x = 10;
console.log(x); // 10
}
10.3 Переменная, "вытекающая" из цикла
javascript
function processItems(items) {
for (var i = 0; i < items.length; i++) {
// обрабатываем item
}
// i всё ещё доступна и равна items.length
// можно случайно использовать i в другом месте
}
Часть 11. Миграция с var на let и const
Пошаговая стратегия
- Замените все var на let - это безопасно в 99% случаев.
- Найдите переменные, которые никогда не меняются, и замените на const.
- Проверьте код на наличие проблем с блочной областью - особенно циклы и условия.
- Убедитесь, что нет повторных объявлений - let их запрещает.
javascript
// До
var name = "Анна";
var age = 25;
var isActive = true;
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// После
const name = "Анна";
let age = 25;
let isActive = true;
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
Часть 12. Сравнительная таблица: var vs let vs const
Часть 13. Реальные примеры из жизни
13.1 Баг из-за всплытия переменной
javascript
// Плохо (с var)
function calculatePrice(items) {
var total = 0;
for (var i = 0; i < items.length; i++) {
var discount = items[i].discount || 0;
total += items[i].price * (1 - discount);
}
// Ошибка: i и discount доступны здесь!
console.log(`Обработано ${i} товаров со скидкой ${discount}`);
return total;
}
// Хорошо (с let/const)
function calculatePrice(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
const discount = items[i].discount || 0;
total += items[i].price * (1 - discount);
}
// Ошибка! i и discount не доступны
// console.log(`Обработано ${i} товаров`); // ReferenceError
return total;
}
13.2 Проблема с асинхронностью в цикле
javascript
// Плохо (с var)
for (var i = 1; i <= 5; i++) {
setTimeout(() => {
console.log(`Запрос ${i}`); // все 6 (или 5? зависит)
}, i * 100);
}
// Вывод: 6, 6, 6, 6, 6 (или 5,5,5,5,5 - зависит от условий)
// Хорошо (с let)
for (let i = 1; i <= 5; i++) {
setTimeout(() => {
console.log(`Запрос ${i}`); // 1, 2, 3, 4, 5
}, i * 100);
}
13.3 Случайная перезапись глобальной переменной
javascript
// Плохо (с var)
var name = "Анна"; // перезаписывает window.name
function saveUserName() {
// ... какой-то код
name = "Борис"; // ой, изменили глобальную переменную
}
saveUserName();
console.log(name); // "Борис"
// Хорошо (с let/const)
let userName = "Анна"; // не попадает в window
function saveUserName() {
let userName = "Борис"; // локальная переменная
// глобальная не изменилась
}
saveUserName();
console.log(userName); // "Анна"
Часть 14. Инструменты для борьбы с var
ESLint: правило no-var
ESLint может запретить использование var в вашем коде.
json
{
"rules": {
"no-var": "error"
}
}
Теперь любой var будет считаться ошибкой.
Автоматическая миграция
bash
# Используйте eslint --fix для автоматической замены var на let/const
npx eslint --fix . --rule 'no-var: error'
Но проверьте результат - иногда var нужно заменить на const, а не на let.
Итог: Манифест современного разработчика
- Забудьте var. В современном JavaScript ему нет места.
- Используйте const по умолчанию - если переменная не будет меняться.
- Используйте let только когда переменная действительно должна меняться.
- Никогда не используйте var - даже если кажется, что "так проще".
- Понимайте var, чтобы читать старый код - но не пишите на нём.
Финальный тест (что выведет?):
javascript
var a = 1;
var a = 2;
console.log(a);
if (true) {
var b = 3;
}
console.log(b);
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
console.log(i);
Ответы: 2, 3, 3, 3, 3, 3
var - это исторический артефакт. Как зазубренный край на старом стекле - он был нужен когда-то, но сейчас только режет руки. Современный JavaScript дал нам let и const. Они предсказуемы, безопасны и делают код понятнее. Используйте их. Всегда. А если увидите var в чужом коде - знайте, что это призрак прошлого, который скоро исчезнет навсегда.