调整字号

setTimeout(0) 的作用

大家都知道 JavaScript 中的 setTimeout() 可用来延迟执行一段代码,如:

setTimeout( function() {
  alert("Hello World");
}, 1000) //延时1秒

今天在网上看到了 setTimeout(fn, 0) 的用法,感到有些疑惑,不明白它和直接执行 fn() 有什么区别,遂搜集了一下相关资料,顺便分享下。

先看一段代码:

function a() {
  setTimeout( function(){
    alert(1)
  }, 0);
  alert(2);
}
a();

代码中的 setTimeout 设为 0,也就是延迟 0ms,看上去是不做任何延迟立刻执行,即依次弹出 “1”、“2”。但实际的执行结果确是 “2”、“1”。其中的原因得从 setTimeout 的原理说起:

JavaScript 是单线程执行的,也就是无法同时执行多段代码,当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个队列,一旦当前任务执行完毕,再从队列中取出下一个任务。这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是 Ajax 请求完成触发了回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执行。假如当前 JavaScript 进程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,仍然要等待前面代码执行完毕。所以 setTimeout 并不能保证执行的时间,是否及时执行取决于 JavaScript 线程是拥挤还是空闲。

至于这样的写法有什么作用,看下面的例子。

<input type="text" onkeydown="show(this.value)">
<div></div>
<script type="text/javascript">
  function show(val) {
    document.getElementsByTagName('div')[0].innerHTML = val;
  }
</script>

这里绑定了 keydown 事件,意图是当用户在文本框里输入字符时,将输入的内容实时地在 <div> 中显示出来。但是实际效果并非如此,你可以在下面的示例中测试:


可以发现,每按下一个字符时,<div> 中只能显示出之前的内容,无法得到当前的字符。这时就可以利用 setTimeout(0)

<input type="text" onkeydown="var self=this; setTimeout(function() {show(self.value)}, 0)">
<div></div>
<script type="text/javascript">
  function show(val) {
    document.getElementsByTagName('div')[0].innerHTML = val;
  }
</script>

原因是,当用户按下按键的时候,JavaScript 引擎需要执行 keydown 的事件处理程序,然后更新文本框的 value 值,这两件事也需要按顺序来,事件处理程序执行时,更新 value 值的任务则进入队列等待。所以我们在 keydown 的事件处理程序里是无法得到更新后的 value 的,利用 setTimeout,我们把取 value 的操作放入队列,放在更新 value 值以后,这样便达到了目的。示例如下,可以发现已经能够实时显示输入的内容。


有人可能会想到利用绑定 keyup 事件来解决,但是 onkeyup 有一个问题,就是当一直按着某个键不放时,也会无法得到输入内容,因为此时不断地触发 keydownkeypress,直到用户松起时才会触发 keyup。当然,示例所用的 keydown 也存在一些小缺点,比如用户如果使用右键粘贴,则无法得到粘贴的内容。

所以最理想的方案应该是使用 HTML5 的 input 事件,当文本框或 textareavalue 发生变化时,就会触发此事件,对粘贴也可以很好地兼容。至于 IE9 之前的浏览器,需要使用专有的 propertychange 事件。

参考文章:

评论(11条)

test 2013年9月11日 , 14:46

这个博客的配色很赞。

奇云tasty 2013年9月11日 , 19:01

Thank you,你是从哪里访问到我的?

2012创业家 2013年9月11日 , 20:52

太赞了

test 2013年9月11日 , 23:03

segmentfault 里一条回答

ponyma 2014年1月24日 , 15:26

Google

阿松 2014年4月18日 , 22:49

『但现在给 alert(1) 加上 setTimeout 后,alert(1) 就被加入到了一个新的堆栈中等待,并“尽可能快”的执行』,这应该是setTimeout产生了一个事件,添加到JS运行时里消息队列的尾部,所以,alert(2)执行完了,函数调用栈清空了,然后才从消息队列中取出刚才的事件,执行回调函数,也就是执行alert(1)。Javascript一个运行时就一个堆栈,没有『新的堆栈』

奇云tasty 2014年4月19日 , 0:53

你说得对,这里应该是一个队列而不是堆栈。
理解很深刻

悟道前端 2014年5月24日 , 9:17

点赞

lxjwlt 2014年6月22日 , 22:12

漂亮!

另外 setTimeout(0) 延迟时间是16ms 或 4ms 取决于浏览器

杨军军 2015年5月20日 , 0:27

例子很好,受教了。

[...] 想要理解上面的2段代码,我们得了解一下javascript中setTimeout的实现原理。首先牢记一点:JavaScript 是单线程执行的,也就是无法同时执行多段代码。下面这段解释来自这篇博客: [...]

flight