Я уже некоторое время пишу на 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
все также остается полезен в ситуациях, когда вы хотите, чтобы переменная имела область видимости во всей функции, но я уверен, что если вы полагаетесь на такую не интуитивную технику для работы вашего алгоритма, то у вас большие проблемы.