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).,