Переполнение стека Javascript при выполнении метода setTimeout + передача переменной

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

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

вот моя функция:

function setblink(id) {
   var elm = document.getElementById(id);
    if (elm.color == "red"){
        elm.color = "black";
    }
    else{
        elm.color = "red";
    }
    setTimeout(setblink(id),500);
}

У меня есть массив «идентификаторов» для элементов, которые должны мигать, называемых repsToBlink.

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

for(var x in repsToBlink){
setTimeout(setblink(repsToBlink[x]),500);
}

Как я могу заставить этот код делать то же самое, не вызывая переполнения стека?

Спасибо!


person Flawed_Logicc    schedule 16.01.2012    source источник
comment
Если вы хотите сделать это с помощью чистого CSS, попробуйте это. Дополнительная информация на MDN. Вам нужно будет добавить больше префиксов для конкретных поставщиков, чтобы он работал с большим количеством браузеров, и он не будет работать в старых браузерах.   -  person seth.miller    schedule 16.01.2012
comment
Я не большой поклонник setTimeout() для каждого элемента. Вместо этого используйте список: jsfiddle.net/HdCbt   -  person Jared Farrish    schedule 16.01.2012


Ответы (3)


Вам нужно изменить setTimeout с

setTimeout(setblink(id),500);

to:

setTimeout(function() { setblink(id) },500);

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

person techfoobar    schedule 16.01.2012

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

Поскольку setblink принимает аргумент, оберните его в анонимную nullary функцию для ленивая оценка.

function setblink(id) {
   var elm = document.getElementById(id);
    if (elm.color == "red"){
        elm.color = "black";
    }
    else{
        elm.color = "red";
    }
    setTimeout(function () {setblink(id)},500);
}

for (var x in repsToBlink){
    (function (id) {
        setTimeout(function () {setblink(id)},500);
    })(repsToBlink[x]);
}

Код требует дополнительных улучшений.

Если repsToBlink является массивом, вам следует перебирать целочисленные индексы repsToBlink (for (...;...;...)), а не свойства (for ... in). Однако, если бы вместо этого вы использовали объект с идентификаторами и индексами (а не значениями), for ... in был бы уместен.

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

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

Всякий раз, когда вы удаляете и id из repsToBlink, проверяйте, остались ли они; если нет, отмените интервал.

(function () {
    var repsToBlink, repCount=0, blinkInterval;

    function startBlinking(ids) {
        addRepsToBlink(ids);
        if (! blinkInterval) {
            blinkInterval = setTimeout(blinkAll, 500);
        }
    }

    function addRepsToBlink(ids) {
        for (var i=0; i<ids.length; ++i) {
            addRep(ids[i]);
        }
    }

    function addRep(id) {
        if (! id in repsToBlink) {
            ++repCount;
            repsToBlink[ids[i]] = true;
        }
    }

    function removeRep(id) {
        if (id in repsToBlink) {
            delete repsToBlink[id];
            --repCount;
            if (!repCount) {
                clearInterval(blinkInterval);
                blinkInterval=0;
            }
        }
    }

    function blinkAll() {
        for (id in repsToBlink) {
            blink(id);
        }
    }

    function blink(id) {
        var elm = document.getElementById(id);
        if (elm.color == "red"){
            elm.color = "black";
        } else {
            elm.color = "red";
        }
    }

    window.startBlinking = startBlinking;
    window.addRepsToBlink = addRepsToBlink;
    window.addRep = addRep;
    window.removeRep = removeRep;
})();
person outis    schedule 16.01.2012

Ваша проблема в том, что setTimeout вызывается в глобальном контексте. Это, и вы немедленно вызываете функцию.

Интерпретатор, когда доберется до этого кода:

setTimeout(setblink(id),500);

немедленно вызывает функцию setblink, предполагая, что возвращаемое значение этой функции - это то, что должен вызывать тайм-аут. Это вызывает переполнение стека, потому что это рекурсивная функция.

Чтобы исправить это, оберните функцию, которую setTimeout должен вызывать, внутри function(){}.

person Jeffrey Sweeney    schedule 16.01.2012