Почему вы больше не должны использовать var

Перевод Why You Shouldn’t Use ‘var’ Anymore, Mark Brouch, 18.10.2016

Я уже некоторое время пишу на JavaScript, используя синтаксис ES2015 (ES6), и могу по достоинству оценить, как новые изменения языка сделали его элегантнее и проще. Одно из первых и самых легких изменений в коде стало использование let/const вместо var. Я сразу оценил удобство let перед var; это не просто новый стильный синтаксис для var, он предоставляет важный механизм ограничения видимости переменной.

В первую очередь стоит отметить, что я объявляю большинство переменных с помощью const вместо let/var где это возможно. const выдаст сообщение об ошибке при попытке изменить ее значение после объявления, полезная функция для предотвращения случайных изменений. Тем не менее, изменяемые переменные тоже нужны, например, в качестве счетчика в цикле. Но почему вы должны использовать функцию let вместо var, если они обе объявляют переменную? Ответ прост. При объявлении через let областью видимости будет блок, в котором объявлена переменная, в отличие от var, для которой областью видимости будет вся функция. Подобное объяснение проще понять на практическом примере.

Позвольте мне продемонстрировать это важное различие на примере классического вопроса, при собеседовании на должность front-end разработчика:

Что будет напечатано в консоли при выполнении следующего кода?

var callbacks = [];
(function() {
  for (var i = 0; i < 5; i++) {
    callbacks.push( function() { return i; } );
  }
})();
console.log(callbacks.map( function(cb) { return cb(); } ));

В этом примере, мы проходим по циклу 5 раз, каждый раз вставляя функцию в массив callback. После заполнения массива 5 функциями, мы запускаем каждую из них, показывая результат в консоли. Начинающий разработчик может ответить (неверно), что результат будет [0, 1, 2, 3, 4], и это разумный ответ, для того, кто не знает про ловушку JavaScript под названием “поднятие” (hoisting).

Правильным ответом будет [5, 5, 5, 5, 5], и станет понятно почему, если мы покажем, что делает механизм “поднятие” за кулисами:

var callbacks = [];
(function() {
  var i;
  for (i = 0; i < 5; i++) {
    callbacks.push( function() { return i; } );
  }
})();
console.log(callbacks.map( function(cb) { return cb(); } ));

Обратите внимание на то, как JavaScript поднимает объявление переменной в начало функции, поэтому значение i будет равно 5 при каждом вызове функции, так как переменной присвоится максимальное значение из цикла до того, как функции будут вызваны.

Есть целый ряд классических способов решить эту проблему и получить в консоли результат [0, 1, 2, 3, 4], но let позволяет сделать это проще:

var callbacks = [];
(function() {
  for (let i = 0; i < 5; i++) {
    callbacks.push( function() { return i; } );
  }
})();
console.log(callbacks.map( function(cb) { return cb(); } ));

Вот и все – просто заменили var на let и пример стал работать как ожидалось! Все благодаря блочной области видимости, задаваемой let. Вместо того, чтобы поднимать объявление переменной в начало функции, let оставляет переменную в области видимости цикла, в результате чего вызывается отдельный экземпляр i на каждой итерации.

Ну как, вы и дальше собираетесь использовать var? Как вы, вероятно, поняли из названия этой статьи, я считаю, что вам необходимо отказаться от var вообще. Технически, var все также остается полезен в ситуациях, когда вы хотите, чтобы переменная имела область видимости во всей функции, но я уверен, что если вы полагаетесь на такую не интуитивную технику для работы вашего алгоритма, то у вас большие проблемы.