Сегодня мы его разберём и поговорим о подходах к поиску ответа. Задавая вопрос, о котором идёт речь, интервьюер предлагает рассказать о том, что выведет примерно такой код:
А вы знаете, что появится в консоли?
Сразу хочется сказать, что этот вопрос направлен на понимание таких механизмов JS, как замыкания, области видимости и функция setTimeout. Правильный ответ выглядит так:
Если вы ожидали чего-то другого, надеемся, в этом материале мы сможем рассказать о том, почему вывод этого фрагмента кода оказался именно таким, и о том, как привести его в более приличный вид.
Почему этот вопрос так популярен?
Один пользователь Reddit рассказал о том, что ему задавали такой вопрос на собеседовании в Amazon. Я и сам сталкивался с подобными вопросами, направленными на понимание циклов и замыканий в JS, даже на собеседовании в Google.
Этот вопрос позволяет проверить владение некоторыми важными концепциями JavaScript. Учитывая особенности работы JS, ситуация, которая смоделирована в представленном фрагменте кода, нередко может возникать и в ходе реальной работы. В частности, это касается использования setTimeout
или какой-нибудь другой асинхронной функции в цикле.
Хорошее понимание функциональных и блочных областей видимости в JavaScript, особенностей устройства анонимных функций, замыканий и IIFE, поможет вашему профессиональному росту и позволит показать себя с хорошей стороны на собеседованиях.
Подходы к ответу на вопрос и к избавлению от undefined
На самом деле, я уже писал о возможных подходах к ответу на этот вопрос в некоторых моих предыдущих материалах. В частности, в этом и этом. Позволю себе процитировать кое-что из этих публикаций:
Причина подобного заключается в том, что функция setTimeout
создаёт функцию (замыкание), у которой есть доступ к внешней по отношению к ней области видимости, представленной в данном случае циклом, в котором объявляется и используется переменная i
. После того, как пройдут 3 секунды, функция выполняется и выводит значение i
, которое, после окончания работы цикла, остаётся доступным и равняется 4-м. Переменная, в ходе работы цикла, последовательно принимает значения 0, 1, 2, 3, 4, причём, последнее значение оказывается сохранённым в ней и после выхода из цикла. В массиве имеется четыре элемента, с индексами от 0 до 3, поэтому, попытавшись обратиться к arr[4]
, мы и получаем undefined
. Как избавиться от undefined
и сделать так, чтобы код выводил то, чего от него и ждут, то есть — значения элементов массива?
Вот пара распространённых подходов к решению подобной задачи, а конкретно — к тому, чтобы организовать доступ к нужному значению переменной цикла внутри функции, вызываемой setTimeout
.
Первый предусматривает передачу необходимого параметра во внутреннюю функцию, второй основан на использовании возможностей ES6.
Итак, вот первый вариант:
Вот второй вариант:
const arr = [10, 12, 15, 21];
for (let i = 0; i < arr.length; i++) {
// использование ключевого слова let, которое появилось в ES6,
// позволяет создавать новую привязку при каждом вызове функции
// подробности смотрите здесь: http://exploringjs.com/es6/ch_variables.html#sec_let-const-loop-heads
setTimeout(function() {
console.log('The index of this number is: ' + i);
}, 3000);
}
На Reddit мне удалось найти похожий ответ на этот вопрос. Вот — хорошее разъяснение особенностей замыканий на StackOverflow.
Итоги
Можно отметить, что вопрос, с которого мы начали этот материал, часто сбивает с толку людей, обладающих небольшим опытом в области JavaScript или в функциональном программировании. Причина заключается в непонимании сущности замыканий. При формировании замыкания не выполняет передача значения переменной или ссылки на неё. Замыкание захватывает саму переменную.