Что будет на выходе данного кода?

Это один из самых частых вопросов, которые задают на собеседованиях по Javascript. Почему ?

Этот вопрос и логика его решения показывают, насколько хорошо вы понимаете области видимости и замыкания в JS.

Вывод данного кода определенно не

0 1 2

It is

3 3 3

Но почему 3 раза по 3? Принимая во внимание, что приведенный ниже код дает желаемый результат.

Это потому, что код здесь выполняется синхронно. Выполнение кода происходит следующим образом:

console.log(0); i = 0
console.log(1); i = 1
console.log(2); i = 2

Принимая во внимание, что с setTimeout мы обернули console.log обратным вызовом. Обратный вызов — это просто некоторый код, который мы хотим запустить после запуска другой функции. Этот обратный вызов запускается после завершения выполнения синхронного кода, когда обратный вызов setTimeout отправляется в веб-API.

Обратный вызов, который мы предоставляем в setTimeout, помещается в очередь событий и выбирается для выполнения после того, как стек вызовов реализует синхронный код.

Так что, безусловно, обратный вызов должен хранить копию i, чтобы использовать его, верно?

Переменная, объявленная с помощью var i, создает переменную в глобальной среде, которая обновляется с каждой итерацией.

Обратный вызов, который мы передаем setTimeout, создает замыкание над внешней средой (переменная i). Таким образом, обратный вызов ссылается на глобальную переменную i.

Переменная, объявленная с помощью var внутри цикла, создает глобальную переменную

Когда цикл завершается, то есть стек вызовов пуст, и теперь настало время циклу обработки событий выбрать код из очереди событий, значение i в глобальной среде равно 3 (значение при котором цикл выходит).

Таким образом, закрытие (обратный вызов в setTimeout ) ссылается на эту глобальную переменную среды i и, следовательно, печатает 3 три раза.

Как мы можем это решить?

Из приведенного выше обсуждения ясно, что решение заключается в создании другой области для i, которая будет существовать в каждый раз, когда мы вызываем setTimeout, чтобы каждый обратный вызов в очереди событий поддерживал ссылка на совершенно другую переменную, и у нас действительно есть 3 разные переменные с именами i в разных областях.

1. Использование let

Использование letдляобъявления переменной i создает другую область действия для i в каждой итерации. Поскольку let имеет блочную область действия, он не создает глобальную переменную для i и создает другую область действия для каждого значения i в цикле. .

Мы также можем создать переменную внутри цикла. Это также даст тот же результат: -

Здесь i по-прежнему остается глобальной переменной, но переменная j ограничена областью блока цикла. Таким образом, для каждой итерации для j создается отдельная область.

2. Использование функции

Каждая функция создает уникальную область. Благодаря этому переменные с одинаковыми именами могут существовать в отдельных функциях и не мешать друг другу.

Когда for loop выполнено, эти три уникальных значения k все еще находятся в памяти, и к ним соответствующим образом обращаются наши операторы console.log(k). Это закрытие.

3. Использование IIFE

Здесь IIFE создает новую область для каждого обратного вызова setTimeout, не нарушая глобальную область или родительскую область.

4. Использование привязки

Функция Function#bind создает новую функцию, которая при вызове будет вызывать исходную функцию, используя первый аргумент как this для вызова и передавая любые дополнительные аргументы. Таким образом, console.log.bind(null, i) создает (но не вызывает) функцию, которая при вызове вызывает console.log с this, установленным на null, и передавая i в качестве первого аргумента.

Источник

https://dev.to/levimeahan/closures-scope-and-the-settimeout-for-loop-question-5bl6

https://www.freecodecamp.org/news/thrown-for-a-loop-understanding-for-loops-and-timeouts-in-javascript-558d8255d8a4