Функция setTimeout в JavaScript не вызывается - PullRequest
5 голосов
/ 04 декабря 2008

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

Функция отправляет два аргумента: изменяемый элемент и максимальная высота элемента. Цель функции - развернуть элемент или «скользить вниз» в постоянном темпе. Я знаю, что, возможно, смогу решить эту проблему с помощью jQuery, но я пробую свою собственную функцию.

function slide_down(element, max_height)
{   
    if(element.clientHeight < max_height)
    {
        var factor = 10;
        var new_height = (element.clientHeight + factor >= max_height) ? max_height : (element.clientHeight + factor);
        element.style.height = new_height + 'px';

        var func_call = 'slide_down(' + element + ', ' + max_height + ');';
        window.setTimeout(func_call, 10);
    }
}

Я проверил аргументы при первоначальном вызове функции. max_height установлен в 20, потому что это желаемая высота элементов (я получил это значение из .scrollHeight).

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

var func_call = 'slide_down(' + element + ', ' + max_height + ');';
window.setTimeout(func_call, 10);

И это не работает. ЭТО работает:

var func_call = 'alert(1);';
window.setTimeout(func_call, 10);

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

Обратите внимание, что высота элемента изменяет первую итерацию, функция просто никогда не вызывает себя. Таким образом, переменная элемента установлена ​​правильно, и я использовал alert () для отображения max_height, что также правильно.

alert(func_call);

Оповещает об этом:

slide_down([object HTMLParagraphElement], 20);

Ответы [ 6 ]

19 голосов
/ 04 декабря 2008

Когда вы делаете это:

var func_call = 'slide_down(' + element + ', ' + max_height + ');';

вы конвертируете элемент в строку, поэтому ваш тайм-аут будет выглядеть как

slide_down("[Object]", 100);

что явно не сработает.

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

function slide_down(element, max_height)
{   
    if(element.clientHeight < max_height)
    {
        var factor = 10;
        var new_height = (element.clientHeight + factor >= max_height) ? max_height : (element.clientHeight + factor);
        element.style.height = new_height + 'px';

        var func = function()
        {
            slide_down(element, max_height);
        }

        window.setTimeout(func, 10);
    }
}

Кроме того, 10 немного для тайм-аута - я бы рекомендовал использовать 50 как минимум.

3 голосов
/ 04 декабря 2008

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

Во-вторых, это потому, что ссылка на элемент теряется в строке concat, так как element.toString () будет "[object]". Попробуйте передать идентификатор, который вы можете найти заново, сохранить ссылку на уровень выше или (как я только что видел, как указывает Мэтт b) форму расширенного метода.

2 голосов
/ 01 ноября 2011

Более тонкий способ написания метода (как продолжение ответа Грега):

function slide_down(element, max_height)
{   
    if(element.clientHeight < max_height)
    { return; }

    var factor = 10,
        new_height = element.clientHeight + factor,
        f = arguments.callee;

    if( new_height > max_height )
    { new_height = max_height; }

    element.style.height = new_height + 'px';

    window.setTimeout(function() { f(element, max_height); }, 10);
}

Реальным преимуществом этого примера кода является использование arguments.callee. Таким образом, вы не нарушите свою функцию, если решите переименовать ее. Я также просто присвоил значение new_height. Проблема в старом коде заключается в том, что вы выполнили element.clientHeight + factor дважды, что довольно странно. Не то чтобы это оказало заметное влияние на вашу производительность в этом случае. Но всегда хорошо избегать многократного вычисления одних и тех же вещей, а просто сохранить результат для последующего использования.

1 голос
/ 04 декабря 2008

Проблема с вашим кодом в том, что вы не можете передать параметр элемента.

Вместо:

var func_call = 'slide_down(' + element + ', ' + max_height + ');';

window.setTimeout(func_call, 10);

попробуйте использовать анонимную функцию:


window.setTimeout(function() {
    slide_down(element, max_height)
}, 10);

@matt.b: передача аргументов в вызов setTimeout (как вы) не поддерживается в IE.

1 голос
/ 04 декабря 2008

У меня был похожий опыт. Когда вы передаете элемент в следующий раз, он преобразуется в строку в вызове функции, поэтому, когда функция выполняется, она пытается найти элемент с именем [object] .string или чем-то, вместо того, что вы намеревались.

Попробуйте изменить параметр функции так, чтобы он брал идентификатор элемента и сначала вызывал document.getElementById () внутри функции.

0 голосов
/ 04 декабря 2008

Две вещи:

1. Существует альтернативный способ вызова setTimeout(), в котором вы передаете функцию и параметры, а не строку для выполнения. Фактически, руководство Gecko DOM гласит , что ваша версия вызова setTimeout() не рекомендуется.

Вместо:

var func_call = 'slide_down(' + element + ', ' + max_height + ');';
window.setTimeout(func_call, 10);

Попробуйте вместо этого:

window.setTimeout(slide_down, 10, element, max_height);  

Обновление : как упомянул Роборг, в IE это может не сработать, поэтому вы можете его игнорировать.


2. У меня такое ощущение, что вызов setTimeout() из функции, которая была вызвана самим setTimeout(), либо недопустим, либо работает неправильно. Я предполагаю, что вы выполняете цепочку вызовов, как это, потому что вы хотите, чтобы код выполнялся повторно? Если так, то для этого window.setInterval().

...