Закрытие JavaScript внутри циклов — простой практический пример

Закрытие JavaScript внутри циклов — простой практический пример

Немного опоздал на вечеринку, но я изучал эту проблему сегодня и заметил, что многие из ответов не полностью касаются того, как Javascript обрабатывает области видимости, что, по сути, сводится к этому.

Итак, как уже упоминалось, проблема в том, что внутренняя функция ссылается на ту же переменную i . Так почему бы нам просто не создавать новую локальную переменную на каждой итерации и вместо этого ссылаться на внутреннюю функцию?

 //overwrite console.log() so you can see the console output console.log = function(msg) {document.body.innerHTML  = '{amp}lt;p{amp}gt;'   msg   '{amp}lt;/p{amp}gt;';}; var funcs = {}; for (var i = 0; i {amp}lt; 3; i  ) { var ilocal = i; //create a new local variable funcs[i] = function() { console.log("My value: "   ilocal); //each should reference its own local variable }; } for (var j = 0; j {amp}lt; 3; j  ) { funcs[j](); } 

Как и раньше, когда каждая внутренняя функция выводила последнее значение, назначенное для i , теперь каждая внутренняя функция просто выводит последнее значение, присвоенное ilocal . Но не должна ли каждая итерация иметь свой собственный ilocal ?

Оказывается, это проблема. Каждая итерация имеет одну и ту же область видимости, поэтому каждая итерация после первой просто перезаписывает ilocal . От MDN :

Важный: у JavaScript нет блочной области видимости. Переменные, введенные с блоком, попадают в область действия содержащей их функции или сценария, и последствия их установки сохраняются за пределами самого блока. Другими словами, операторы блока не вводят область действия. Хотя «автономные» блоки являются допустимым синтаксисом, вы не хотите использовать автономные блоки в JavaScript, потому что они не делают то, что вы думаете, они делают, если вы думаете, что они делают что-то вроде таких блоков в C или Java.

Подтверждено для акцента:

Мы можем убедиться в этом, проверив ilocal перед тем, как объявить это в каждой итерации:

 //overwrite console.log() so you can see the console output console.log = function(msg) {document.body.innerHTML  = '{amp}lt;p{amp}gt;'   msg   '{amp}lt;/p{amp}gt;';}; var funcs = {}; for (var i = 0; i {amp}lt; 3; i  ) { console.log(ilocal); var ilocal = i; } 

Именно поэтому этот баг такой хитрый. Даже если вы переделываете переменную, Javascript не выдаст ошибку, а JSLint даже не выдаст предупреждение. Вот почему лучший способ решить эту проблему — воспользоваться преимуществами замыканий, что, по сути, означает, что в Javascript внутренние функции имеют доступ к внешним переменным, поскольку внутренние области «заключают» внешние области.

Затворы

Это также означает, что внутренние функции «держат» внешние переменные и поддерживают их, даже если внешняя функция возвращается. Чтобы использовать это, мы создаем и вызываем функцию-обертку исключительно для создания новой области, объявляем ilocal в новой области и возвращаем внутреннюю функцию, которая использует ilocal (более подробное объяснение ниже):

 //overwrite console.log() so you can see the console output console.log = function(msg) {document.body.innerHTML  = '{amp}lt;p{amp}gt;'   msg   '{amp}lt;/p{amp}gt;';}; var funcs = {}; for (var i = 0; i {amp}lt; 3; i  ) { funcs[i] = (function() { //create a new scope using a wrapper function var ilocal = i; //capture i into a local var return function() { //return the inner function console.log("My value: "   ilocal); }; })(); //remember to run the wrapper function } for (var j = 0; j {amp}lt; 3; j  ) { funcs[j](); } 

Создание внутренней функции внутри функции-оболочки дает внутренней функции частную среду, к которой только она может получить доступ, «замыкание». Таким образом, каждый раз, когда мы вызываем функцию-обертку, мы создаем новую внутреннюю функцию со своей собственной отдельной средой, гарантируя, что ilocal переменные не ilocal и не перезаписывают друг друга. Несколько небольших оптимизаций дают окончательный ответ, который дали многие другие пользователи SO:

 //overwrite console.log() so you can see the console output console.log = function(msg) {document.body.innerHTML  = '{amp}lt;p{amp}gt;'   msg   '{amp}lt;/p{amp}gt;';}; var funcs = {}; for (var i = 0; i {amp}lt; 3; i  ) { funcs[i] = wrapper(i); } for (var j = 0; j {amp}lt; 3; j  ) { funcs[j](); } //creates a separate environment for the inner function function wrapper(ilocal) { return function() { //return the inner function console.log("My value: "   ilocal); }; } 

Обновить

Теперь ES6 стал массовым, теперь мы можем использовать новое ключевое слово let для создания блочных переменных:

 //overwrite console.log() so you can see the console output console.log = function(msg) {document.body.innerHTML  = '{amp}lt;p{amp}gt;'   msg   '{amp}lt;/p{amp}gt;';}; var funcs = {}; for (let i = 0; i {amp}lt; 3; i  ) { // use "let" to declare "i" funcs[i] = function() { console.log("My value: "   i); //each should reference its own local variable }; } for (var j = 0; j {amp}lt; 3; j  ) { // we can use "var" here without issue funcs[j](); } 

Посмотрите, как это просто сейчас! Для получения дополнительной информации см. Этот ответ , на котором основана моя информация.

§

СЧИТАЕТСЯ ПРИМИТИВНЫМ

Давайте определим функции обратного вызова следующим образом:

 // **************************** // COUNTER BEING A PRIMITIVE // **************************** function test1() { for (var i=0; i{amp}lt;2; i  ) { setTimeout(function() { console.log(i); }); } } test1(); // 2 // 2 

По истечении времени ожидания он напечатает 2 для обоих. Это связано с тем, что функция обратного вызова обращается к значению на основе лексической области действия , в которой была определена функция.

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

 function test2() { function sendRequest(i) { setTimeout(function() { console.log(i); }); } for (var i = 0; i {amp}lt; 2; i  ) { sendRequest(i); } } test2(); // 1 // 2 

Теперь в этом особенность: «Примитивы передаются по значению и копируются. Таким образом, когда определено замыкание, они сохраняют значение из предыдущего цикла».

СЧЕТЧИК БЫТЬ ОБЪЕКТОМ

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

 // **************************** // COUNTER BEING AN OBJECT // **************************** function test3() { var index = { i: 0 }; for (index.i=0; index.i{amp}lt;2; index.i  ) { setTimeout(function() { console.log('test3: '   index.i); }); } } test3(); // 2 // 2 

Таким образом, даже если для переменной, переданной в качестве объекта, создано замыкание, значение индекса цикла не будет сохранено. Это показывает, что значения объекта не копируются, а доступ к ним осуществляется через ссылку.

 function test4() { var index = { i: 0 }; function sendRequest(index, i) { setTimeout(function() { console.log('index: '   index); console.log('i: '   i); console.log(index[i]); }); } for (index.i=0; index.i{amp}lt;2; index.i  ) { sendRequest(index, index.i); } } test4(); // index: { i: 2} // 0 // undefined // index: { i: 2} // 1 // undefined 
Понравилась статья? Поделиться с друзьями:
JavaScript & TypeScript
Adblock
detector