【编辑推荐】JavaScript下的setTimeout(fn,0)意味着什么?

近期在研究异步编程的我对于setTimeout之类的东西异常敏感。在SegmentFault上看到了一个问题《关于SetTimeout时间设为0时》:提问者读了一篇文章,原文解释setTimeout延迟时间为0时会发生的事情,提问者提出了几个文章中的几个疑点。读了那篇文章之后发现原文的作者对于setTimeout的理解和自己的认知有点出入,于是编写了相关测试的代码以求答案。最终编写了这篇文章。

本文内容如下:

起因单线程的JavaScriptsetTimeout背后意味着什么参考和引用

JavaScript – 前端开发交流群:377786580

起因

上午在SegmentFault上看到了这个问题《关于SetTimeout 时间设为0时》(注:SegmentFault正在调整备案,如不能访问,请点击这里),原提问者注明了问题来源:《JS setTimeout延迟时间为0的详解》。这个问题来源也是转载的,我后来找到了出处。在问题来源的那篇的文章中(后者),讲述了JS是单线程引擎:它把任务放到队列中,不会同步去执行,必须在完成一个任务后才开始另外一个任务。而后,转载的那篇文章列出并补充了原文的栗子:

>=="text/javascript">{return document.getElementById(id);}window.onload = function () {//第一个例子:未使用setTimeoutget(‘makeinput’).onmousedown = function () {var input = document.createElement(‘input’);input.setAttribute(‘type’, ‘text’);input.setAttribute(‘value’, ‘test1’);get(‘inpwrapper’).appendChild(input);input.focus();input.select();}//第二个例子:使用setTimeoutget(‘makeinput2’).onmousedown = function () {var input = document.createElement(‘input’);input.setAttribute(‘type’, ‘text’);input.setAttribute(‘value’, ‘test1’);get(‘inpwrapper2’).appendChild(input);//setTimeoutsetTimeout(function () {input.focus();input.select();}, 0);}//第三个例子,onkeypress输入的时候少了一个值get(‘input’).onkeypress = function () {get(‘preview’).innerHTML = this.value;}}>>==>2、使用 ==>3、另一个例子===></body></html>

代码运行实例请戳这里。原文中有这么一段话,描述的有点抽象:

JavaScript引擎在执行onmousedown时,由于没有多线程的同步执行,不可能同时去处理刚创建元素的focus 和select方法,由于这两个方法都不在队列中,在完成onmousedown后,JavaScript 引擎已经丢弃了这两个任务,正如第一种情况。而在第二种情况中,由于setTimeout可以把任务从某个队列中跳脱成为新队列,因而能够得到期望的结果。

我看到这里就觉得非常不对劲了。因为按照这种任务会被丢弃的说法,那么只要在事件触发的函数中再触发其他的事件都会被丢弃,浏览器是绝对不会这么做的,于是我编写了测试代码:

window.onload = function () {//第一个例子:未使用setTimeoutget(‘makeinput’).onmousedown = function () {var input = document.createElement(‘input’);input.setAttribute(‘type’, ‘text’);input.setAttribute(‘value’, ‘test1’);get(‘inpwrapper’).appendChild(input);//按照文中的理论,这里的click不会被触发,但它却成功触发了get(‘inpwrapper’).click();//触发了inpwrapper的onclick事件}get(‘inpwrapper’).onclick = function () {alert(‘linkFly’);};}

下面的onclick()最终是执行了:弹出了"linkFly"。

而在转载的文中为了引人深思,又提出了第三个例子:

在此,你可以看看例子 3,它的任务是实时更新输入的文本,现在请试试,你会发现预览区域总是落后一拍,比如你输 a, 预览区并没有出现 a, 在紧接输入b时,a才不慌不忙地出现。

而文中最后留给大家的思考的问题,解决方案就是使用setTimeout再次调整浏览器的代码任务运行队列。

var domInput = get(‘input’);domInput.onkeypress = function () {setTimeout(function () {//第三个例子的问题就这样就会被解决get(‘preview’).innerHTML = domInput.value;})}

原文和转载的文章中都对setTimeout(fn,0)进行了思考,但原文指出的问题本质漏洞百出,所以才出了这篇文章,我们的正文,现在开始。

单线程的JavaScript

首先我们来看浏览器下的JavaScript:浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程,GUI渲染线程,浏览器事件触发线程。

javascript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意 GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。(当线程中没有执行任何同步代码的前提下才会执行异步代码)每一天都不可追回,所以更要珍惜每一寸光阴,

【编辑推荐】JavaScript下的setTimeout(fn,0)意味着什么?

相关文章:

你感兴趣的文章:

标签云: