最近都在看一些JavaScript原理层面的文章,恰巧看到了jQuery的做者的一篇关于JavaScript计时器原理的解析,因而坐卧不安地决定把原文翻译成中文,一来是为了和你们分享,二来是为了加深本身对于JavaScript的理解。原文连接:http://ejohn.org/blog/how-javascript-timers-work/javascript
原文翻译:html
从基础层面来说,理解JavaScript计时器的工做原理是很重要的。因为JavaScript是单线程的,因此不少时候计时器并非表现得和咱们的直观想象同样。让咱们从下面的三个函数开始,它们可以让咱们有机会去构造和操做计时器。java
var id =setTimeout(fn, delay)
; 建立了一个简单的计时器,在通过给定的时间后,回调函数将会被执行。这个函数会返回一个惟一的ID,便于在以后某个时间能够注销这个计时器。var id = setInterval(fn, delay)
; -和setTimeout相似,可是每通过一段时间(给定的延时),所传递的函数就会被执行一次,直到这个定时器被注销。clearInterval(id)
; clearTimeout(id); -接受一个计时器ID(由以前两种计时器返回)而且中止计时器回调函数的执行。为了理解计时器的内部工做原理,咱们首先须要了解一个很是重要的概念:计时器设定的延时是没有保证的。由于全部在浏览器中执行的JavaScript单线程异步事件(好比鼠标点击事件和计时器)都只有在它有空的时候才执行。这最好经过图片来讲明,就以下面这张图所示:node
这一张图片里面有不少信息须要慢慢消化,可是完全地理解这张图片将会让你对JavaScript异步执行是如何工做的有一个更好的认识。这张图片是从一维的角度来阐述的:在垂直方向是以毫秒计的时间,蓝色的块表明了git
当前正在执行的JavaScript代码段。好比第一段JavaScript执行了大概18毫秒,鼠标点击事件大概执行了11毫秒。github
因为JavaScript每次只能执行一段代码(基于它单线程的特性),因此全部这些代码段都阻塞了其余异步事件的执行。这就意味着,当一件异步事件(好比鼠标点击,计时器触发和一个XMLHttpRequest 请求完成)触发的时候,这些事件的回调函数将排在执行队列的最后去等待执行(排队的方式因浏览器不一样而不一样,这里只是一个简化)。segmentfault
一开始,在第一段代码段内,两个计时器被初始化:一个10ms的setTimeout 和一个10ms的setInterval。因为计时器在哪儿初始化就在那儿开始计时,因此实际上计时器在第一段代码执行完成以前就触发了。然而,计时器的回调函数并非当即执行了(单线程限制了不能这样作),相反的是,回调函数排在了执行队列的最后,等到下一个有空的时间去执行。浏览器
此外,在第一个代码块内咱们看到了一个鼠标点击事件发生了。与之相关的javascript异步事件(咱们不可能预测用户会在何时去采起这样的动做,所以这个事件被视为异步的)并不会当即执行。和计时器同样的是,它被放到了队列的最后去等待执行。异步
在第一个代码快执行完成的时候,浏览器会当即发出这样的询问:谁正在等待执行?这个时候,鼠标点击处理程序和计时器回调函数都在等待执行。浏览器选择了其中一个(鼠标点击回调函数)而且当即执行它。为了执行,计时器会等到下一个可能执行的时间。async
咱们注意到,当鼠标点击事件对应的处理程序正在执行的时候,第一个定时回调函数也要执行了。同定时计时器同样,它也在队列的后面等待执行。然而,咱们能够注意到,当定时器再一次触发(在计时器回调函数正在执行的时候),这一次定时器回调函数被丢弃了。若是在执行一大块代码块的时候,你把全部的定时回调函数都放在队列的最后,结果就是一大串定时回调函数将会没有间隔的一块儿执行,直到完成。相反,在把更多定时回调函数放到队列以前,浏览器会静静的等待,知道队列中的全部定时回调函数都执行完成。
事实上,咱们能够看到,当interval回调函数正在执行的时候,interval第三次被触发。这给咱们一个很重要的信息:interval并不关心当前谁在执行,它的回调函数会不加区分地进入队列,即便存在这个回调函数会被丢弃的可能。
最后,当第二个定时回调函数完成执行的时候,咱们能够看到javascript引擎已经没有什么须要执行了。这意味着,浏览器如今正在等待一个新的异步事件的发生。咱们能够看到在50ms的时候,定时回调函数再一次被触发。然而,这一次,没有其余代码阻塞他的执行了,因此他当即执行了定时回调函数。
让咱们看一个例子来更好地阐述setTimeout 和setInterval的区别。
1 setTimeout(function(){ 2 /* Some long block of code... */ 3 setTimeout(arguments.callee, 10); 4 }, 10); 5 6 setInterval(function(){ 7 /* Some long block of code... */ 8 }, 10);
第一眼看上去这两段代码在功能上是等价的,但事实上却不是。值得注意的是,setTimeout 这段代码会在每次回调函数执行以后至少须要延时10ms再去执行一次(多是更多,可是不会少)。可是setInterval会每隔10ms就去尝试执行一次回调函数,无论上一个回调函数是否是还在执行。
从这里咱们可以学到不少,让咱们来归纳一下:
我的看法:
翻译完成以后,感受对于javascript异步有了新的认识,可是可能初学者看不太懂这篇文章,因而写了一个demo,运行在nodejs环境下(浏览器不容易模拟)
1 var startTime = new Date(); 2 3 //初始化计时器 4 var start = setTimeout(function() { 5 var end = new Date(); 6 console.log('10ms的计时器执行完成,距离程序开始' + (end - start) + 'ms'); 7 }, 10); 8 9 //模拟鼠标点击事件 10 function asyncReal(data, callback) { 11 process.nextTick(function() { 12 callback(); 13 }); 14 } 15 var asyncStart = new Date(); 16 asyncReal('yuanzm', function() { 17 var asyncEnd = new Date(); 18 console.log('模拟鼠标执行事件完成,花费时间' + (asyncEnd - asyncStart) + 'ms'); 19 }) 20 21 //设定定时器 22 count = 1; 23 var interval = setInterval(function() { 24 ++count; 25 if(count === 5) { 26 clearInterval(interval); 27 } 28 console.log('定时器事件'); 29 },10); 30 31 //模拟第一阶段代码执行 32 var first = []; 33 var start = new Date(); 34 for(var i = 0;i < 10000000;i++){ 35 first.push(i); 36 } 37 var end = new Date(); 38 console.log('第一阶段代码执行完成,用时' + (end - start) + 'ms');
运行结果以下:
咱们按照文中的原理来解释一下:
郑重声明
本文章属于我的原创,如需转载,请加上原文连接:
http://segmentfault.com/a/1190000002633108
另外一样能够在博客园上面查看本文章:http://www.cnblogs.com/yuanzm/p/4126762.html
也欢迎Follow个人Github:https://github.com/yuanzm