Хитрый JavaScript

Часто бывает возникает ситуация, когда в цикле надо сгенерировать несколько зависимых от значения в массиве callback-ов. Часто такая ситуация возникает при работе с несколькими ajax запросами.  Какие есть проблемы ?


Пример ситуации(простейший).
Есть у нас функция whatToDo принимающая в качестве единственного аргумента callback с действием, на определенный элемент. Функция whatToDo выполняет каллбек не сразу.
И массив элементов, на которые эти действия должны производиться. Не заглядывая в простоту задачи — посмотрим ситуацию.

var ids = [‘2′,’3′,’4′,’7’];
for (var i in ids){
whatToDo(function(){
document.getElementById(i).innerHTML = ‘Hello’;
})
}

Ситуация типовая — Мы надеемся, что в идентификаторах, которые соответствуют значениям массива ids, будет текст ‘Hello’. Так вот — можно удивится, но это не так.
Давайте разберем ситуацию по полочкам вначале.
С помощью цикла for in мы пробегаемся по массиву ids. При каждой итерации, значение переменной i будет перезаписываться новым значением из массива ids.
Внутри цикла мы вызываем функцию whatToDo, которой передаем анонимную функцию. Переменная i замыкается. Но переменная передается по ссылке, а не по значению, и соответственно во всех каллбеках, будет в i — последнее значение массива (в нашем случае это 7).

Т.е. получится что на момент первого вызова
function(){
document.getElementById(i).innerHTML = ‘Hello’;
}

в i вполне может оказатся 7 а не 2. А может и 3 или 4 — смотря что будет в i на момент выполнения цикла. Особенно если вызовы асинхронны.

Как же сделать так, чтобы значения нормально передавались? Делать какой-то внешний стек?
К счастью, есть решения и в таком случае.

var ids = [‘2′,’3′,’4′,’7’];
for (var i in ids){
(function(localVar){
whatToDo(function(){
document.getElementById(localVar).innerHTML = ‘Hello’;
})
})(i);
}

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

Пример крайне простой, и просто симулирована типовая ситуация. Задача была в том, чтобы показать, как можно решать подобные задачи без костылей.

Если тема шаблонов проектирования и их реализации кому интересна — могу описать =)

2 комментария to “Хитрый JavaScript”

  1. Михаил Комм:

    Я в таком случае стараюсь не злоупотреблять замыканиями и делаю так (может быть кстати и не очень хорошо — поправьте меня, если что):

    var arr = [];

    for ( var i = 0; i < 3; i++ ) {
    arr[i] = function() {
    alert( arguments.callee.i );
    };
    arr[i].i = i;
    }

    arr[0]();
    arr[1]();
    arr[2]();
    собсно arguments.callee — это ссылка НА САМУ ФУНКЦИЮ, а .i — в данном контексте ее статический член.

    • Олег Барабанов:

      Вариант ясен — но он подходит для отдельной задачи. ну и вызов всеравно приходится делать. Вариант который я описал, самодостаточен и компактен, а также не оставляет мусора в области видимости другими переменными.
      А чем если не секрет не угодили замыкания в JS ? Не спорю — в IE(основательно вроде до 6-го включительно) они иногда приводят к утечкам памяти. Но замыкания это очень мощный инструмент и ситуации, когда он необходим, особенно в асинхронном режиме, очень много. Преимущества думаю не стоит перечислять — но было бы очень интересно и полезно знать — всетаки, чем мотивирована такая осторожность к замыканиям.

Коментарии