john resig写的一篇文章:javascript
原文地址:http://ejohn.org/blog/how-javascript-timers-work/ html
做为入门者来讲,了解JavaScript中timer的工做方式是很重要的。一般它们的表现行为并非那么地直观,而这是由于它们都处在一个单一线程中。让咱们先来看一看三个用来建立以及操做timer的函数。java
var id = setTimeout(fn, delay);
- 初始化一个单一的timer,这个timer将会在必定延时后去调用指定的函数。这个函数(setTimeout)将返回一个惟一的ID,咱们能够经过这个ID来取消timer。var id = setInterval(fn, delay);
- 与setTimeout相似,只不过它会持续地调用指定的函数(每次都有一个延时),直到timer被取消为止。clearInterval(id);
, clearTimeout(id);
- 接受一个timer的ID(由上述的两个函数返回的),而且中止timer的回调事件。要搞明白timer在 内部是怎么工做的,咱们还须要知道一个很重要的概念::定时器的延时是没有保证的。因为全部在游览器执行的js都是单线程异步事件(好比鼠标单击和定时器),执行过程当中只有在有空闲的时候才会被执行。经过下图能够很好的说明这一观点:面试
在上图中有不少信息是须要好好去消化下的,彻底理解会让你对js的异步工做有更好的认识。上面图表是一维的:垂直表示时间,按毫秒计算。蓝色的盒子表示正在执行的部分js。如第一个js块执行的时间大约为18ms,鼠标点击块执行的时间大约为11ms,等等。
由于js只能在某一时刻执行一小段代码(因为它的单线程天性),这些执行代码块中的每一个都会"阻塞"其余异步事件的进行。这意味着当一个异步事件发生时(如鼠标单击事件,定时器触发,或者异步请求完成时),它会排队等待执行(至于队列其实是如何排列的,想必各个游览器表现都会不同,因此这样考虑是一个简化)。
ajax
刚开始,在第一个JavaScript块中,有两个timer被 初始化了:一个10ms的setTimeout和一个是10ms的setInterval。因为timer(这里的timer指setTimeout中的 timer,而下文中的interval则指setInvertal中的timer)开始的时间,实际上它在第一个代码块结束前就已经触发了。然而请注 意,它并不会立刻执行(事实上因为单线程的关系,它也无法作到立刻执行)。相反的,这个被延期执行的函数进入队列中,等待在空闲的时候被执行。编程
此外,在第一个代码块里,咱们看到一个鼠标单击事件发生。一个js回调函数被绑定于这个异步事件(咱们不知道用户什么时候会点击,因此这被认为是异步的),但不会被立刻执行,它像开始的那个定时器同样,会进入队列等待执行。json
在第一个js代码块初始化完成后,游览器会马上询问:谁正在等待执行啊?这种状况下,鼠标单击事件处理函数和定时器回调函数正在等待执行。而后游览器挑选一个(鼠标单击事件处理函数)立刻执行,定时器回调函数继续等待下一个可能的时间去执行。浏览器
注意当鼠标点击事件正在执行的时候第一次的interval事件也触发了,与timer一 样,它的事件也进入队列等待以后执行。然而,注意,当interval再次触发的时候(这个时候timer的事件正在执行),这一次它的事件被丢弃了。如 果你在一个大的JavaScript代码块正在执行的时候把全部的interval回调函数都囤起来的话,其结果就是在JavaScript代码块执行完 了以后会有一堆的interval事件被执行,而执行过程当中不会有间隔。所以,取代的做法是浏览器情愿先等一等,以确保在一个interval进入队列的 时候队列中没有别的interval。多线程
事实上,咱们能够在例子中看出:当第三个interval触发的时候这个interval自身正在执行。这告诉咱们一个重要的事实:interval是无论当前在执行些什么的,在任何状况下它都会进入到队列中去,即便这样意味着每次回调之间的时间就不许确了。app
最后,当第二个interval回调执行完后,咱们能够看到队列已经被清空,没有什么须要JavaScript引擎去执行的了。这代表浏览器如今等 待一个新的异步事件发生。因而在50ms的时候咱们看到interval又触发了。这同样,因为没有什么东西挡住了它的执行,它立刻就触发了。
让咱们来看一个例子,这个例子更好地阐释了setTimeout和setInveral之间的区别。
乍看上去,这两段代码在功能上彷佛是相同的,可实际上并不是如此。setTimeout的代码在前一次的回调执行完后老是至少会有10ms的延时(有 可能会更多,可是绝对不会更少);而setInterval则老是在每10ms的时候尝试执行一次回调,它无论上一次回调是何时执行的。
咱们在此学到了不少,让咱们重述一下:
setTimeout和
setInterval在如何执行代码上有着本质地区别。
以上这些知识是至关重要的。知道JavaScript引擎的工做方式,尤为是知道它在有不少异步事件发生的时候是怎么工做的,为咱们在写进阶的应用程序代码打下了坚实的基础。
某人总结:
总结以下:
所以,对于动画来讲,若是单帧的执行时间大于间隔时间,用setTimeout比用setInterval更保险。John Resig在回复中也代表了这个观点:
It really depends on the situation – and how the timers are actually being used. setInterval will, most likely, get you more ‘frames’ in the animation but will certainly tax your processor more. A lot of frameworks end up using setTimeout since it degrades more gracefully on slower computers.
一个简单的测试页面:timer_test.html(请在Chrome下运行,注意那些零值或接近零的值,setInterval没有interval了!)
所以,在这种状况下,采用setTimeout更保险:
setTimeout(function(){ setTimeout(arguments.callee, 10); }, 10);
固然,大部分状况下,单帧执行时间都小于预设的间隔时间,上面分析的差别,是感受不大出来的。
------------------------
一道JavaScript面试题(setTimeout)
下面的代码,多久以后会弹出'end'? 为何?
var t = true;
setTimeout(function(){ t = false; }, 1000);
while(t){ }
alert('end');
这是之前在想有没办法实现阻塞javascript线程的时候(即实现sleep方法),想过的一种实现。
很简单,是吧?
是吗?
答案是:典型的死循环……js是单线程执行的,while里面死掉的时候setTimeout里面的函数是没机会执行的。
一、简单的settimeout
setTimeout(function () { while (true) { } }, 1000); setTimeout(function () { alert('end 2'); }, 2000); setTimeout(function () { alert('end 1'); }, 100); alert('end');
执行的结果是弹出‘end’‘end 1’,而后浏览器假死,就是不弹出‘end 2’。也就是说第一个settimeout里执行的时候是一个死循环,这个直接致使了理论上比它晚一秒执行的第二个settimeout里的函数被阻塞,这个和咱们平时所理解的异步函数多线程互不干扰是不符的。
二、ajax请求回调
接着咱们来测试一下经过xmlhttprequest实现ajax异步请求调用,主要代码以下:
var xmlReq = createXMLHTTP();//建立一个xmlhttprequest对象 function testAsynRequest() { var url = "/AsyncHandler.ashx?action=ajax"; xmlReq.open("post", url, true); xmlReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlReq.onreadystatechange = function () { if (xmlReq.readyState == 4) { if (xmlReq.status == 200) { var jsonData = eval('(' + xmlReq.responseText + ')'); alert(jsonData.message); } else if (xmlReq.status == 404) { alert("Requested URL is not found."); } else if (xmlReq.status == 403) { alert("Access denied."); } else { alert("status is " + xmlReq.status); } } }; xmlReq.send(null); } testAsynRequest();//1秒后调用回调函数 while (true) { }
在服务端实现简单的输出:
private void ProcessAjaxRequest(HttpContext context) { string action = context.Request["ajax"]; Thread.Sleep(1000);//等1秒 string jsonObject = "{\"message\":\"" + action + "\"}"; context.Response.Write(jsonObject); }
理论上,若是ajax异步请求,它的异步回调函数是在单独一个线程中,那么回调函数必然不被其余线程”阻挠“而顺利执行,也就是1秒后,它回调执行弹出‘ajax’,但是实际状况并不是如此,回调函数没法执行,由于浏览器再次由于死循环假死。
结论:根据实践结果,能够得出,javascript引擎确实是单线程处理它的任务队列(能理解成就是普通函数和回调函数构成的队列吗?)的。在javascript里实现异步编程很大程度上就是一种障眼法,单线程的引擎实现多线程的编程,若是要实现一些资源同步互斥之类的操做(一如C#、Java等语言的多线程),我感受真正实现起来根本没法轻易获得保证。
补充:如何实现javascript的sleep呢?在stackoverflow上找到一篇javascript sleep,试了一下,效果是有了,可是执行的时候cpu很高,真还不如直接settimeout呢。
转自:http://www.cnblogs.com/jeffwongishandsome/archive/2011/06/13/2080145.html
容易欺骗别人感情的JavaScript定时器
JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,由于咱们开始经常觉得调用了就会按既定的方式执行, 我想很多人都深有同感, 例如
setTimeout(function() { alert('你好!'); }, 0); setInterval(callbackFunction, 100);
认为setTimeout中的问候方法会当即被执行,由于这并非凭空而说,而是JavaScript API文档明肯定义第二个参数意义为隔多少毫秒后,回调方法就会被执行. 这里设成0毫秒,理所固然就当即被执行了.
同理对setInterval的callbackFunction方法每间隔100毫秒就当即被执行深信不疑!
但随着JavaScript应用开发经验不断的增长和丰富,有一天你发现了一段怪异的代码而百思不得其解:
div.onclick = function(){ setTimeout(function() { document.getElementById('inputField').focus(); }, 0); };
既然是0毫秒后执行,那么还用setTimeout干什么, 此刻, 坚决的信念已开始动摇.
直到最后某一天 , 你不当心写了一段糟糕的代码:
setTimeout(function() { while (true) { } }, 100); setTimeout(function() { alert('你好!'); }, 200); setInterval(callbackFunction, 200);
第一行代码进入了死循环,但不久你就会发现,第二,第三行并非预料中的事情,alert问候未见出现,callbacKFunction也杳无音讯!
这时你完全迷惘了,这种情景是难以接受的,由于改变长久以来既定的认知去接受新思想的过程是痛苦的,但情事实摆在眼前,对JavaScript真理的探求并不会由于痛苦而中止,下面让咱们来展开JavaScript线程和定时器探索之旅!
拔开云雾见月明
出现上面全部误区的最主要一个缘由是:潜意识中认为,JavaScript引擎有多个线程在执行,JavaScript的定时器回调函数是异步执行的.
而事实上的,JavaScript使用了障眼法,在多数时候骗过了咱们的眼睛,这里背光得澄清一个事实:
JavaScript引擎是单线程运行的,浏览器不管在何时都只且只有一个线程在运行JavaScript程序.
JavaScript引擎用单线程运行也是有意义的,单线程没必要理会线程同步这些复杂的问题,问题获得简化.
那么单线程的JavaScript引擎是怎么配合浏览器内核处理这些定时器和响应浏览器事件的呢?
下面结合浏览器内核处理方式简单说明.
浏览器内核实现容许多个线程异步执行,这些线程在内核制控下相互配合以保持同步.假如某一浏览器内核的实现至少有三个常驻线程:javascript引擎线程,界面渲染线程,浏览器事件触发线程,除些之外,也有一些执行完就终止的线程,如Http请求线程,这些异步线程都会产生不一样的异步事件,下面经过一个图来阐明单线程的JavaScript引擎与另外那些线程是怎样互动通讯的.虽然每一个浏览器内核实现细节不一样,但这其中的调用原理都是大同小异.
Js线程图示
由图可看出,浏览器中的JavaScript引擎是基于事件驱动的,这里的事件可看做是浏览器派给它的各类任务,这些任务能够源自JavaScript引擎当前执行的代码块,如调用setTimeout添加一个任务,也可来自浏览器内核的其它线程,如界面元素鼠标点击事件,定时触发器时间到达通知,异步请求状态变动通知等.从代码角度看来任务实体就是各类回调函数,JavaScript引擎一直等待着任务队列中任务的到来.因为单线程关系,这些任务得进行排队,一个接着一个被引擎处理.
上图t1-t2..tn表示不一样的时间点,tn下面对应的小方块表明该时间点的任务,假设如今是t1时刻,引擎运行在t1对应的任务方块代码内,在这个时间点内,咱们来描述一下浏览器内核其它线程的状态.
t1时刻:
GUI渲染线程:
该线程负责渲染浏览器界面HTML元素,当界面须要重绘(Repaint)或因为某种操做引起回流(reflow)时,该线程就会执行.本文虽然重点解释JavaScript定时机制,但这时有必要说说渲染线程,由于该线程与JavaScript引擎线程是互斥的,这容易理解,由于JavaScript脚本是可操纵DOM元素,在修改这些元素属性同时渲染界面,那么渲染线程先后得到的元素数据就可能不一致了.
在JavaScript引擎运行脚本期间,浏览器渲染线程都是处于挂起状态的,也就是说被”冻结”了.
因此,在脚本中执行对界面进行更新操做,如添加结点,删除结点或改变结点的外观等更新并不会当即体现出来,这些操做将保存在一个队列中,待JavaScript引擎空闲时才有机会渲染出来.
GUI事件触发线程:
JavaScript脚本的执行不影响html元素事件的触发,在t1时间段内,首先是用户点击了一个鼠标键,点击被浏览器事件触发线程捕捉后造成一个鼠标点击事件,由图可知,对于JavaScript引擎线程来讲,这事件是由其它线程异步传到任务队列尾的,因为引擎正在处理t1时的任务,这个鼠标点击事件正在等待处理.
定时触发线程:
注意这里的浏览器模型定时计数器并非由JavaScript引擎计数的,由于JavaScript引擎是单线程的,若是处于阻塞线程状态就计不了时,它必须依赖外部来计时并触发定时,因此队列中的定时事件也是异步事件.
由图可知,在这t1的时间段内,继鼠标点击事件触发后,先前已设置的setTimeout定时也到达了,此刻对JavaScript引擎来讲,定时触发线程产生了一个异步定时事件并放到任务队列中, 该事件被排到点击事件回调以后,等待处理.
同理, 仍是在t1时间段内,接下来某个setInterval定时器也被添加了,因为是间隔定时,在t1段内连续被触发了两次,这两个事件被排到队尾等待处理.
可见,假如时间段t1很是长,远大于setInterval的定时间隔,那么定时触发线程就会源源不断的产生异步定时事件并放到任务队列尾而无论它们是否已被处理,但一旦t1和最早的定时事件前面的任务已处理完,这些排列中的定时事件就依次不间断的被执行,这是由于,对于JavaScript引擎来讲,在处理队列中的各任务处理方式都是同样的,只是处理的次序不一样而已.
t1事后,也就是说当前处理的任务已返回,JavaScript引擎会检查任务队列,发现当前队列非空,就取出t2下面对应的任务执行,其它时间依此类推,由此看来:
若是队列非空,引擎就从队列头取出一个任务,直到该任务处理完,即返回后引擎接着运行下一个任务,在任务没返回前队列中的其它任务是无法被执行的.
相信您如今已经很清楚JavaScript是否可多线程,也了解理解JavaScript定时器运行机制了,下面咱们来对一些案例进行分析:
案例1:setTimeout与setInterval
setTimeout(function() { /* 代码块... */ setTimeout(arguments.callee, 10); }, 10); setInterval(function(){ /*代码块... */ }, 10);
这两段代码看一块儿效果同样,其实非也,第一段中回调函数内的setTimeout是JavaScript引擎执行后再设置新的setTimeout定时, 假定上一个回调处理完到下一个回调开始处理为一个时间间隔,理论两个setTimeout回调执行时间间隔>=10ms .第二段自setInterval设置定时后,定时触发线程就会源源不断的每隔十秒产生异步定时事件并放到任务队列尾,理论上两个setInterval回调执行时间间隔<=10.
案例2:ajax异步请求是否真的异步?
不少同窗朋友搞不清楚,既然说JavaScript是单线程运行的,那么XMLHttpRequest在链接后是否真的异步?
其实请求确实是异步的,不过这请求是由浏览器新开一个线程请求(参见上图),当请求的状态变动时,若是先前已设置回调,这异步线程就产生状态变动事件放到JavaScript引擎的处理队列中等待处理,当任务被处理时,JavaScript引擎始终是单线程运行回调函数,具体点即仍是单线程运行onreadystatechange所设置的函数.
-------------------------------
转自:http://www.cnblogs.com/dolphinX/archive/2013/04/05/2784933.html
setTimeout()和setInterval()常常被用来处理延时和定时任务。setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式,而setInterval()则能够在每隔指定的毫秒数循环调用函数或表达式,直到clearInterval把它清除。
从定义上咱们能够看到两个函数十分相似,只不过前者执行一次,然后者能够执行屡次,两个函数的参数也相同,第一个参数是要执行的code或句柄,第二个是延迟的毫秒数。
很简单的定义,使用起来也很简单,但有时候咱们的代码并非按照咱们的想象精确时间被调用的,很让人困惑
看个简单的例子,简单页面在加载完两秒后,写下Delayed alert!
setTimeout('document.write("Delayed alert!");', 2000);
看起来很合理,咱们再看个setInterVal()方法的例子
var num = 0; var i = setInterval(function() { num++; var date = new Date(); document.write(date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds() + '<br>'); if (num > 10) clearInterval(i); }, 1000);
页面每隔1秒记录一次当前时间(分钟:秒:毫秒),记录十次后清除,再也不记录。考虑到代码执行时间可能记录的不是执行时间,但时间间隔应该是同样的,看看结果
43:38:116 43:39:130 43:40:144 43:41:158 43:42:172 43:43:186 43:44:200 43:45:214 43:46:228 43:47:242 43:48:256
时间间隔几乎是1000毫秒,但不精确,这是为何呢?缘由在于咱们对JavaScript定时器存在一个误解,JavaScript实际上是运行在单线程的环境中的,这就意味着定时器仅仅是计划代码在将来的某个时间执行,而具体执行时机是不能保证的,由于页面的生命周期中,不一样时间可能有其余代码在控制JavaScript进程。在页面下载完成后代码的运行、事件处理程序、Ajax回调函数都是使用一样的线程,实际上浏览器负责进行排序,指派某段程序在某个时间点运行的优先级。
咱们把效果放大一下看看,添加一个耗时的任务
function test() { for (var i = 0; i < 500000; i++) { var div = document.createElement('div'); div.setAttribute('id', 'testDiv'); document.body.appendChild(div); document.body.removeChild(div); } } setInterval(test, 10); var num = 0; var i = setInterval(function() { num++; var date = new Date(); document.write(date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds() + '<br>'); if (num > 10) clearInterval(i); }, 1000);
咱们又加入了一个定时任务,看看结果
47:9:222 47:12:482 47:16:8 47:19:143 47:22:631 47:25:888 47:28:712 47:32:381 47:34:146 47:35:565 47:37:406
这下效果明显了,差距甚至都超过了3秒,并且差距很不一致。
咱们能够能够把JavaScript想象成在时间线上运行。当页面载入的时候首先执行的是页面生命周期后面要用的方法和变量声明和数据处理,在这以后JavaScript进程将等待更多代码执行。当进程空闲的时候,下一段代码会被触发
除了主JavaScript进程外,还须要一个在进程下一次空闲时执行的代码队列。随着页面生命周期推移,代码会按照执行顺序添加入队列,例如当按钮被按下的时候他的事件处理程序会被添加到队列中,并在下一个可能时间内执行。在接到某个Ajax响应时,回调函数的代码会被添加到队列。JavaScript中没有任何代码是当即执行的,但一旦进程空闲则尽快执行。定时器对队列的工做方式是当特定时间过去后将代码插入,这并不意味着它会立刻执行,只能表示它尽快执行。
知道了这些后,咱们就能明白,若是想要精确的时间控制,是不能依赖于JavaScript的setTimeout函数的。
使用 setInterval() 建立的定时器可使代码循环执行,到有指定效果的时候,清除interval就能够,以下例
var my_interval = setInterval(function () { if (condition) { //.......... } else { clearInterval(my_interval); } }, 100);
但这个方式的问题在于定时器的代码可能在代码再次被添加到队列以前尚未执行完成,结果致使循环内的判断条件不许确,代码多执行几回,之间没有停顿。不过JavaScript已经解决这个问题,当使用setInterval()时,仅当没有该定时器的其余代码实例时才将定时器代码插入队列。这样确保了定时器代码加入到队列的最小时间间隔为指定间隔。
这样的规则带来两个问题
为了不这两个缺点,咱们可使用setTimeout()来实现重复的定时器
setTimeout(function () { //code setTimeout(arguments.callee, interval); }, interval)
这样每次函数执行的时候都会建立一个新的定时器,第二个setTimeout()调用使用了agrument.callee 来获取当前实行函数的引用,并设置另一个新定时器。这样作能够保证在代码执行完成前不会有新的定时器插入,而且下一次定时器代码执行以前至少要间隔指定时间,避免连续运行。
setTimeout(function () { var div = document.getElementById('moveDiv'); var left = parseInt(div.style.left) + 5; div.style.left = left + 'px'; if (left < 200) { setTimeout(arguments.callee, 50); } }, 50);
这段定时器代码每次执行的时候,把一个div向右移动5px,当坐标大于200的时候中止。