Что будет на выходе данного кода?
Это один из самых частых вопросов, которые задают на собеседованиях по 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