Вызов функций с помощью setTimeout () - PullRequest
36 голосов
/ 27 сентября 2010

Проще говоря ...

, почему

setTimeout('playNote('+currentaudio.id+', '+noteTime+')', delay);

работает идеально, вызывая функцию после указанной задержки, но

setTimeout(playNote(currentaudio.id,noteTime), delay);

вызывает функциюplayNote все одновременно?

(эти setTimeout () находятся в цикле for)

или, если мое объяснение слишком сложно для чтения, в чем разница между двумя функциями

Ответы [ 6 ]

67 голосов
/ 27 сентября 2010

Первая форма, которую вы перечислите, работает, так как она будет оценивать строку в конце delay.Использование eval(), как правило, не очень хорошая идея, поэтому вам следует избегать этого.

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

Вместо этого вы должны передать анонимную функцию в setTimeout,поэтому правильная форма:

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);

Обратите внимание, что вы передаете setTimeout целое выражение функции, поэтому оно будет удерживать анонимную функцию и выполнять ее только в конце задержки.

Вы также можете передать setTimeout ссылку, так как ссылка не выполняется сразу, но тогда вы не можете передать аргументы:

setTimeout(playNote, delay);

Примечание:

Для повторяющихся событий вы можете использовать setInterval(), и вы можете установить setInterval() для переменной и использовать переменную, чтобы остановить интервал с помощью clearInterval().

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

Просто используйте рекурсивную функцию, чтобы обойти всю эту проблему.

Использование рекурсии для работы с переменным временем задержки:

  // Set original delay
var delay = 500;

  // Call the function for the first time, to begin the recursion.
playNote(xxx, yyy);

  // The recursive function
function playNote(theId, theTime)
{
    // Do whatever has to be done
    // ...

    // Have the function call itself again after a delay, if necessary
    //   you can modify the arguments that you use here. As an
    //   example I add 20 to theTime each time. You can also modify
    //   the delay. I add 1/2 a second to the delay each time as an example.
    //   You can use a condition to continue or stop the recursion

    delay += 500;

    if (condition)
    { setTimeout(function() { playNote(theID, theTime + 20) }, delay); }
}
7 голосов
/ 27 сентября 2010

Попробуйте это.

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
4 голосов
/ 27 сентября 2010

Не используйте строковые таймауты.Это эффективный eval, что плохо.Это работает, потому что он конвертирует currentaudio.id и noteTime в строковые представления себя и скрывает это в коде.Это работает только до тех пор, пока эти значения имеют toString() s, которые генерируют синтаксис литерала JavaScript, который воссоздает значение, что верно для Number, но не для многих других.

setTimeout(playNote(currentaudio.id, noteTime), delay);

, это вызов функции,playNote вызывается немедленно, и возвращенный результат функции (вероятно, undefined) передается setTimeout(), а не тому, что вы хотите.

Как уже упоминалось в других ответах, вы можете использовать встроенное выражение функции сзамыкание на ссылку currentaudio и noteTime:

setTimeout(function() {
    playNote(currentaudio.id, noteTime);
}, delay);

Однако, если вы находитесь в цикле, а currentaudio или noteTime каждый раз отличается от цикла, вы получаетепроблема замкнутого цикла: в каждый тайм-аут будет ссылаться одна и та же переменная, поэтому при их вызове вы будете получать одно и то же значение каждый раз, то значение, которое оставалось в переменной, когда цикл завершался ранее.

Вы можете обойти это с другим закрытием, беря копию значения переменной для каждой итерации цикла:

setTimeout(function() {
    return function(currentaudio, noteTime) {
        playNote(currentaudio.id, noteTime);
    };
}(currentaudio, noteTime), delay);

, но теперь это становится немного уродливым.Лучше будет Function#bind, что частично применимо для вас:

setTimeout(playNote.bind(window, currentaudio.id, noteTime), delay);

(window - для установки значения this внутри функции, которая является функцией bind()Вам здесь не нужно.)

Однако это функция пятого издания ECMAScript, которую поддерживают не все браузеры.Так что, если вы хотите использовать его, вы должны сначала взломать поддержку, например ::

// Make ECMA262-5 Function#bind work on older browsers
//
if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        if (arguments.length<=1) {
            return function() {
                return that.apply(owner, arguments);
            };
        } else {
            var args= Array.prototype.slice.call(arguments, 1);
            return function() {
                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
            };
        }
    };
}
1 голос
/ 27 сентября 2010

Потому что второй вы говорите ему вызвать функцию playNote first , а затем передать возвращаемое значение из него в setTimeout.

0 голосов
/ 18 января 2019

Это может помочь понять, когда javascript выполняет код и когда он ожидает что-то выполнить:

let foo2 = function foo(bar=baz()){ console.log(bar); return bar()}

  • Первое, что выполняет javascript - это конструктор функции, исоздает функциональный объект.Вы можете использовать любой синтаксис ключевого слова функции синтаксиса =>, и вы получите аналогичные (, но не идентичные ) результаты.
  • Затем только что созданная функция присваивается переменной foo2
  • На данный момент больше ничего не было запущено: никакие другие функции не вызывались (ни baz, ни bar, ни поиск значений и т. Д. Однако синтаксис был проверен внутри функции.
  • Если вы передадите foo или foo2 в setTimeout, то после тайм-аута она вызовет функцию, так же, как если бы вы ввели foo(). (Обратите внимание, что аргументы не передаются foo. Это потому, что setTimeout по умолчанию не передает аргументы, , хотя может , но эти аргументы оцениваются до истечения времени ожидания, а не после его истечения.)
  • ПослеВызывается foo, оцениваются аргументы по умолчанию. Поскольку мы вызываем foo без передачи аргументов, оценивается значение по умолчанию для bar. (Этого не произошло бы, если бы мы передали аргумент)
  • При оценке значения по умолчаниюt аргумент для bar, первый javascript ищет переменную с именем baz.Если он находит его, он пытается вызвать его как функцию.Если это работает, он сохраняет возвращаемое значение в bar.
  • Теперь вычисляется основная часть функции:
  • Javascript ищет переменную bar и затем вызывает console.logс результатом.Это не вызывает бар.Однако, если бы он вместо этого назывался bar(), тогда сначала запускался бы bar, а затем вместо этого возвращаемое значение bar() передавалось бы console.log.Обратите внимание, что javascript получает значения аргументов для функции, которую он вызывает до того, как вызывает функцию, и даже до того, как он ищет функцию, чтобы узнать, существует ли она и действительно ли она является функцией.
  • Javascript снова ищет bar, а затем пытается вызвать его как функцию.Если это работает, значение возвращается как результат foo()

Таким образом, тела функций и аргументы по умолчанию не вызываются сразу, а все остальное -.Аналогично, если вы выполняете вызов функции (то есть ()), то эта функция также выполняется немедленно.Однако вы не обязаны вызывать функцию.Оставив скобки, вы сможете передать эту функцию и вызвать ее позже.Недостатком этого является то, что вы не можете указать аргументы, с которыми вы хотите вызывать функцию.Кроме того, javascript выполняет все действия в скобках функции до , вызывает функцию или ищет переменную, в которой хранится функция.

0 голосов
/ 27 июля 2017

Я буквально создал учетную запись на этом сайте, чтобы комментировать ответ Питера Айтая (в настоящее время проголосовал больше всего), только чтобы обнаружить, что вам нужно 50 повторений (независимо от того, что это), поэтому я сделаю это как ответ, так каквероятно, стоит указать пару вещей.

В своем ответе он заявляет следующее:

Вы также можете передать setTimeout ссылку, так как ссылка не выполняется немедленно, но тогда вы не можете передать аргументы:

setTimeout(playNote, delay);

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

setTimeout(playNote, delay, currentaudio.id, noteTime)

Всегда обращайтесь к документации.

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

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

...