foreach для массива проходит не по всем элементам

Рейтинг: 1Ответов: 3Опубликовано: 15.04.2015

На странице есть несколько элементов клики по которым делают их checked (использую кастомный атрибут data-checked). Повторный клик снимает это состояние. Всё работает хорошо, но нужно сделать функцию которая бы очищала все выделенные элементы и возвращала бы их в исходное состояние. Приделал кнопку, ей приделал функцию, но она не работает. Т.е. когда я нажимаю например на 3-4 метки, и вызываю эту функцию то выделение снимается не со всех. Попробуйте выделить (нажать и сделать серыми) 4 метки, а затем вызвать функцию очистки. выделение снимается лишь с двух. Это какая-то ерунда.

http://jsfiddle.net/FzZ2H/24/

Вот та функция которая делает обход , даже в консоли пишется iteration меньшее количество раз, чем ожидается.

var uncheck_all_cells = function () {
    var labels = document.getElementsByClassName("selected_cell");
    Array.prototype.forEach.call(labels, function (label) {
        uncheck_cell(label);
        console.log("iteration");
    });

    console.log(labels);
};

Ответы

▲ 5Принят

Это довольно интересная, но хорошо известная ловушка.

Проблема в том, что labels изменяется, когда вы меняете класс его элементов (в вашем случае вызываете uncheck_cell). Что происходит дальше, очевидно — forEach итерирует уже неверно, поскольку элементы сдвигаются.

Как обойти?

Самое простейшее решение — проходить список в обратном порядке.

for (var i = labels.length - 1; i >= 0; --i) {
    uncheck_cell(labels[i]);
    console.log("iteration");
};

Рабочая версия: http://jsfiddle.net/FzZ2H/25/

Так же имейте в виду, что getElementsByClassName есть не во всех браузерах, в частности его нет в IE9.

Свой собственный getElementsByClassName можно реализовать следующим образом:

function getElementsByClassName(node, classname) {
    var a = [];
    var re = new RegExp('(^| )'+classname+'( |$)');
    var els = node.getElementsByTagName("*");
    for(var i=0,j=els.length; i<j; i++)
        if(re.test(els[i].className))a.push(els[i]);
    return a;
}

Подробнее об этом: https://stackoverflow.com/questions/7410949/

▲ 2

Ну, вообще лучше превратить HTML коллекцию в настоящий массив:

var labels = document.getElementsByClassName("selected_cell");
labels = Array.prototype.slice.call(labels);

Теперь labels - массив, со всеми доступными методами, а дальше делаем так

labels.forEach(function (label) {
    uncheck_cell(label);
});

Этот способ работает: демо

▲ 2

Если вы всё равно используете jQuery, то почему бы не использовать его по полной?

var uncheck_cell = function (label) {
  $(label).removeClass("selected_cell").attr('data-checked', 'false');
};
var check_cell = function (label) {
  $(label).addClass("selected_cell").attr('data-checked', 'true');
};
var uncheck_all_cells = function () {
  var labels = $(".selected_cell");
  labels.each(function() {
    uncheck_cell($(this));
  });
};

Всё намного короче и прекрасно работает