Функции вызова с помощью setTimeout()

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

почему

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

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

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

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

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

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

Ответ 1

Первая форма, которую вы перечислите, работает, так как она будет оценивать строку в конце 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); }
}

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

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

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 Fifth Edition, которая еще не поддерживается всеми браузерами. Поэтому, если вы хотите использовать его, вы должны сначала взломать поддержку, например:

// 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)));
            };
        }
    };
}

Ответ 4

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

Ответ 5

Это может помочь понять, когда 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. (Этого бы не случилось, если бы мы передали аргумент)
  • При оценке аргумента по умолчанию для bar первый javascript ищет переменную с именем baz. Если он находит его, он пытается вызвать его как функцию. Если это работает, он сохраняет возвращаемое значение в bar.
  • Теперь оценивается основная часть функции:
  • Javascript ищет переменную bar и затем вызывает console.log с результатом. Это не вызывает бар. Однако, если бы он вместо этого вызывался как bar(), сначала запустился бы bar, а затем возвращаемое значение bar() было бы передано в console.log. Обратите внимание, что javascript получает значения аргументов для функции, которую он вызывает, до того, как она вызывает функцию, и даже до того, как она ищет функцию, чтобы узнать, существует ли она и действительно ли она является функцией.
  • Javascript снова ищет bar, а затем пытается вызвать ее как функцию. Если это работает, значение возвращается как результат foo()

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

Ответ 6

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

В своем ответе он утверждает следующее:

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

setTimeout(playNote, delay);

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

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

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

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

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