Javascript: создание функций в цикле For

Недавно я обнаружил, что мне нужно создать массив функций. Функции используют значения из документа XML, и я пробегаю соответствующие узлы с циклом for. Однако, сделав это, я обнаружил, что только последний узел листа XML (соответствующий последнему запуску цикла for) когда-либо использовался всеми функциями в массиве.

Ниже приведен пример, демонстрирующий это:

var numArr = [];
var funArr = [];
for(var i = 0; i < 10; ++i){
    numArr[numArr.length] = i;
    funArr[funArr.length] = function(){  return i; };
}

window.alert("Num: " + numArr[5] + "\nFun: " + funArr[5]());

Вывод Num: 5 и Fun: 10.

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

var funArr2 = [];
for(var i = 0; i < 10; ++i)
    funArr2[funArr2.length] = (function(i){ return function(){ return i;}})(i);

window.alert("Fun 2: " + funArr2[5]());

Я знаю, что это связано со сферой охвата, но на первый взгляд не похоже, что она будет работать иначе, чем мой наивный подход. Я немного новичок в Javascript, так что, если я могу спросить, почему использование этой техники, возвращающей функцию, обходит проблему определения объема? Кроме того, почему (I) включен в конце?

Заранее большое спасибо.

3 ответа

Решение

Второй метод немного понятнее, если вы используете имя параметра, которое не маскирует имя переменной цикла:

funArr[funArr.length] = (function(val) { return function(){  return val; }})(i);

Проблема с вашим текущим кодом в том, что каждая функция является замыканием, и все они ссылаются на одну и ту же переменную i, Когда каждая функция запускается, она возвращает значение i во время выполнения функции (которая будет на единицу больше, чем предельное значение для цикла).

Более понятным способом было бы написать отдельную функцию, которая возвращает требуемое замыкание:

var numArr = [];
var funArr = [];
for(var i = 0; i < 10; ++i){
    numArr[numArr.length] = i;
    funArr[funArr.length] = getFun(i);
}

function getFun(val) {
    return function() { return val; };
}

Обратите внимание, что это делает в основном то же самое, что и первая строка кода в моем ответе: вызов функции, которая возвращает функцию, и передачу значения i в качестве параметра. Главное его преимущество - ясность.

РЕДАКТИРОВАТЬ: теперь, когда EcmaScript 6 поддерживается почти везде (извините, пользователи IE), вы можете обойтись более простым подходом - используйте let ключевое слово вместо var для переменной цикла:

var numArr = [];
var funArr = [];
for(let i = 0; i < 10; ++i){
    numArr[numArr.length] = i;
    funArr[funArr.length] = function(){  return i; };
}

С этим небольшим изменением каждый funArr элемент связан с закрытием сделать другое i объект на каждой итерации цикла. Для получения дополнительной информации о letПосмотрите этот пост Mozilla Hacks от 2015 года. (Если вы нацелены на среды, которые не поддерживают letпридерживайтесь того, что я написал ранее, или пропустите это через транспортер перед использованием.

Давайте рассмотрим, что делает код немного ближе, и назначим мнимые имена функций:

(function outer(i) { 
    return function inner() { 
        return i;
    }
 })(i);

Вот, outer получает аргумент i, JavaScript использует область видимости функции, что означает, что каждая переменная существует только внутри функции, в которой она определена. i здесь определяется в outerи, следовательно, существует в outer (и любые области видимости, заключенные внутри).

inner содержит ссылку на переменную i, (Обратите внимание, что он не переопределяет i в качестве параметра или с var ключевое слово!) В общих правилах JavaScript указано, что такая ссылка должна быть связана с первой включающей областью, которая здесь является областью действия outer, Следовательно, i в inner относится к тому же i это было внутри outer,

Наконец, после определения функции outerмы сразу называем это, передавая ему значение i (которая является отдельной переменной, определенной во внешней области видимости). Значение i заключен в outerи его значение теперь не может быть изменено никаким кодом в самой внешней области видимости. Таким образом, когда самый внешний i увеличивается в for петля, i в outer сохраняет то же значение.

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

Наконец, для полноты давайте рассмотрим, что произошло с исходным кодом:

for(var i = 0; i < 10; ++i){
    numArr[numArr.length] = i;
    funArr[funArr.length] = function(){  return i; };
}

Здесь мы видим, что анонимная функция содержит ссылку на самый внешний i, При изменении этого значения оно будет отражено в анонимной функции, которая не сохраняет свою собственную копию значения в любой форме. Таким образом, так как i == 10 во внешней области во время, когда мы идем и вызываем все эти функции, которые мы создали, каждая функция будет возвращать значение 10,

Я рекомендую подобрать книгу, подобную JavaScript: "Полное руководство", чтобы глубже понять JavaScript в целом, чтобы вы могли избежать распространенных ошибок, подобных этой.

Этот ответ также дает достойное объяснение закрытиям:

Как работают JavaScript-закрытия?

Когда вы вызываете

function() { return i; }

функция на самом деле выполняет поиск переменных в родительском объекте вызова (области видимости), где и определяется i. В этом случае i определяется как 10, и поэтому каждая из этих функций возвращает 10. Причина, по которой это работает

(function(i){ return function(){ return i;}})(i);

заключается в том, что путем немедленного вызова анонимной функции создается новый объект вызова, в котором определяется текущий i. Поэтому, когда вы вызываете вложенную функцию, эта функция ссылается на объект вызова анонимной функции (которая определяет любое значение, которое вы передали ей, когда она была вызвана), а не на область, в которой я был первоначально определен (которая по-прежнему равна 10).,

Другие вопросы по тегам