javascript

Хитрый вопрос по JavaScript, который задают на собеседованиях в Google и Amazon

  • вторник, 17 октября 2017 г. в 03:13:59
https://habrahabr.ru/company/ruvds/blog/340194/
  • Разработка веб-сайтов
  • Занимательные задачки
  • JavaScript
  • Блог компании RUVDS.com


Привет Хабр! Есть один вопрос, с виду — не такой уж и сложный, который нередко задают разработчикам на собеседованиях.

Сегодня мы его разберём и поговорим о подходах к поиску ответа. Задавая вопрос, о котором идёт речь, интервьюер предлагает рассказать о том, что выведет примерно такой код:

const arr = [10, 12, 15, 21];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function() {
    console.log('Index: ' + i + ', element: ' + arr[i]);
  }, 3000);
}

А вы знаете, что появится в консоли?

Сразу хочется сказать, что этот вопрос направлен на понимание таких механизмов JS, как замыкания, области видимости и функция setTimeout. Правильный ответ выглядит так:

Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined
Index: 4, element: undefined

Если вы ожидали чего-то другого, надеемся, в этом материале мы сможем рассказать о том, почему вывод этого фрагмента кода оказался именно таким, и о том, как привести его в более приличный вид.

Почему этот вопрос так популярен?


Один пользователь 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 (var i = 0; i < arr.length; i++) {
  // передадим функции переменную i, в результате
  // у каждой функции будет доступ к правильному значению индекса
  setTimeout(function(i_local) {
    return function() {
      console.log('The index of this number is: ' + i_local);
    }
  }(i), 3000);
}

Вот второй вариант:

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 или в функциональном программировании. Причина заключается в непонимании сущности замыканий. При формировании замыкания не выполняет передача значения переменной или ссылки на неё. Замыкание захватывает саму переменную.

Уважаемые читатели! Знаете ли вы интересные вопросы, которые задают на собеседованиях по JavaScript? Если да — просим поделиться.