没有延迟的setTimeout和立即执行函数一样吗?

57
我正在查看一个网络应用程序中的一些现有代码,我看到了这个:

window.setTimeout(function () { ... })

这是否与立即执行函数的概念相同?

Aishwar,你能接受angusC的答案吗?你接受的那个不太正确,而angusC则解释了原因。 - Lynn
4个回答

101

setTimeout不一定会立即运行,即使将延迟明确设置为0也是如此。原因是setTimeout将函数从执行队列中移除,并且它只会在JavaScript完成当前执行队列后才被调用。

console.log(1);
setTimeout(function() {console.log(2)});
console.log(3);
console.log(4);
console.log(5);
//console logs 1,3,4,5,2

了解更多详情,请参见http://javascriptweblog.wordpress.com/2010/06/28/understanding-javascript-timers/


这只是其中一个原因。还有很多其他的原因。 - steinybot

20

setTimeout函数有一个最短延迟时间(HTML5规定为4毫秒,Firefox 3.6为10毫秒)。有关此问题的讨论详见Mozilla开发者中心的文档页面


2
实际上,查看其他答案以获得正确的解释! - Qwerty
根据HTML标准,只有在嵌套级别大于5(或Chrome、Firefox和可能的其他浏览器大于3)时才强制执行最小值。 - steinybot

2
简短的回答是不一样的。
MDN setTimeout 文档中,关于参数delay的描述如下:
“定时器在执行指定的函数或代码之前等待的时间(以毫秒为单位)。如果省略此参数,则使用0,表示立即执行,或更准确地说,在下一个事件循环中执行。请注意,在任何情况下,实际延迟可能比预期的要长;请参阅下面的延迟超过指定时间的原因。”
省略delay或使用0将在下一个事件循环中执行,但可能需要更长的时间。这是它与直接执行函数内容不同的第一个原因。例如:

document.getElementById("runNow").addEventListener("click", runNow);
document.getElementById("runNoDelay").addEventListener("click", runNoDelay);

function runNow() {
  clearLog();
  addLog("Start");
  addLog("Hello");
  addLog("End");
}

function runNoDelay() {
  clearLog();
  addLog("Start");
  setTimeout(function() {
    addLog("Hello");
  });
  addLog("End");
}

function clearLog() {
  const log = document.getElementById("log");
  while (log.lastElementChild) {
    log.removeChild(log.lastElementChild);
  }
}

function addLog(message) {
  const newLine = document.createElement("pre");
  newLine.textContent = message;
  document.getElementById("log").appendChild(newLine);
}
<button id="runNow">Run Now</button>
<button id="runNoDelay">Run With No Delay</button>
<div id="log"></div>

请注意,当使用无延迟的setTimeout时,“End”消息会在“Hello”之前出现。
即使省略延迟或将其设置为0,也有可能需要更长时间,原因可能包括:
  • "......浏览器在调度了5次嵌套的setTimeout后将强制执行最小超时时间为4毫秒。"
  • 浏览器选项卡处于非活动状态。
  • 脚本被识别为跟踪脚本并被限制速度。
  • 队列中的其他任务执行时间比延迟时间长。
  • 由于某种原因,浏览器决定使用其他实现定义的时间长度。
以下是一个示例:

document.getElementById("run").addEventListener("click", run);

function run() {
  clearLog();
  const now = new Date().getMilliseconds();
  setTimeout(function() {
    timeout(0, now);
  });
}

function clearLog() {
  const log = document.getElementById("log");
  while (log.lastElementChild) {
    log.removeChild(log.lastElementChild);
  }
}

function timeout(nestingLevel, last) {
  const now = new Date().getMilliseconds();
  logline(nestingLevel, now, last);
  if (nestingLevel < 9) {
    setTimeout(function() {
      timeout(nestingLevel + 1, now);
    });
  }
}

function logline(nestingLevel, now, last) {
  const newLine = document.createElement("pre");
  newLine.textContent = `${nestingLevel}                ${now - last}`;
  document.getElementById("log").appendChild(newLine);
}
<button id="run">Run</button>
<pre>nesting level    delay</pre>
<div id="log"></div>

(改编自 MDN 上的示例)。

请注意,直到嵌套级别达到一定点时,延迟时间为 0(或接近 0)。

还要注意,setTimeout 使用包装函数(function () { ... })调用,这意味着如果立即执行函数内容,this 的值将与包装函数相同。


Chrome(版本 92.0.4515.131)和 Firefox(版本 91.0)在嵌套级别为 4 时强制最小超时为 4 毫秒,如上面的示例所示。

HTML 标准 表示:“如果嵌套级别大于 5,并且超时小于 4,则将超时设置为 4”。

我的理解是,这些浏览器将此最小超时强制应用于第 7 个任务的调度,其中嵌套级别为 6(大于 5)。

归根结底,如果浏览器希望使用其他实现定义的时间长度,那么这是一个有趣但无关紧要的问题。


0

你缺少了毫秒参数...

setTimeout(function() { /*something*/ }, 0);

0 将延迟设置为 0,但实际上它的作用是让您的函数"跳过队列"浏览器执行列表。 浏览器有很多事情要做,例如呈现页面上的对象,通过调用此函数,您的函数将在浏览器有一些周期时立即运行。


13
题目在问当没有提供延迟时会发生什么。 - Nick Craver
哦,好的。我以为这是语法错误,因为各种文档都说它是必需参数。然后我很快试了一下,发现它可以工作。 - Gary
第二个参数(不再)是必需的。https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout - Matthew Lock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接