JavaScrip单线程引擎工作原理分析


Posted in Javascript onSeptember 04, 2010

从基础的层面来讲,理解JavaScript的定时器是如何工作的是非常重要的。定时器的执行常常和我们的直观想象不同,那是因为JavaScript引擎是单线程的。我们先来认识一下下面三个函数是如何控制计时器的。

三水点靠木推荐阅读:雕虫无小技 JavaScript初学者的10个迷你技巧

var id = setTimeout(fn, delay);

初始化一个计时器,然后在指定的时间间隔后执行。该函数返回一个唯一的标志ID(Number类型),我们可以使用它来取消计时器。

var id = setInterval(fn, delay);

和setTimeout有些类似,但它是连续调用一个函数(时间间隔是delay参数)直到它被取消。
clearInterval(id);, clearTimeout(id);

使用计时器ID(setTimeout 和 setInterval的返回值)来取消计时器回调的发生。

为了理解计时器的内在执行原理,有一个重要的概念需要加以探讨:计时器的延迟(delay)是无法得到保障的。由于所有JavaScript代码是在一个线程里执行的,所有异步事件(例如,鼠标点击和计时器)只有拥有执行机会时才会执行。用一个很好的图表加以说明:
JavaScrip单线程引擎工作原理分析
在这个图表中有许多信息需要理解,如果完全理解了它们,你会对JavaScript引擎如何实现异步事件有一个很好的认识。这是一个一维的图标:垂直方向表示时间,蓝色的区块表示JavaScript代码执行块。例如第一个JavaScript代码执行块需要大约18ms,鼠标点击所触发的代码执行块需要11ms等等。

由于JavaScript引擎同一时间只执行一条代码(这是由于JavaScript单线程的性质),所以每一个JavaScript代码执行块会“阻塞”其它异步事件的执行。这就意味着当一个异步事件发生(例如,鼠标点击,计时器被触发,或者Ajax异步请求)后,这些事件的回调函数将排在执行队列的最后等待执行(实际上,排队的方式根据浏览器的不同而不同,所以这里只是一个简化);

从第一个JavaScript执行块开始研究,在第一个执行块中两个计时器被初始化:一个10ms的setTimeout()和一个10ms的setInterval()。依据何时何地计时器被初始化(计时器初始化完毕后就会开始计时),计时器实际上会在第一个代码块执行完毕前被触发。但是,计时器上绑定的函数不会立即执行(不被立即执行的原因是JavaScript是单线程的)。实际上,被延迟的函数将依次排在执行队列的最后,等待下一次恰当的时间再执行。

此外,在第一个JavaScript执行块中我们看到了一个“鼠标点击”事件发生了。一个JavaScript回调函数绑定在这个异步事件上了(我们从来不知道用户什么时候执行这个(点击)事件,因此认为它是异步的),这个函数不会被立即执行,和上面的计时器一样,它将排在执行队列的最后,等待下一次恰当的时候执行。

当第一个JavaScript执行块执行完毕后,浏览器会立即问一个问题:哪个函数(语句)在等待被执行?在这时,一个“鼠标点击事件处理函数”和一个“计时器回调函数”都在等待执行。浏览器会选择一个(实际上选择了“鼠标点击事件的处理函数”,因为由图可知它是先进队的)立即执行。而“计时器回调函数”将等待下次适合的时间执行。

注意,当“鼠标点击事件处理函数”执行的时候,setInterval的回调函数第一次被触发了。和setTimeout的回调函数一样,它将排到执行队列的最后等待执行。但是,一定要注意这一点:当setInterval回调函数第二次被触发时(此时setTimeout函数仍在执行)setTimeout的第一次触发将被抛弃掉。当一个很长的代码块在执行时,可能把所有的setInterval回调函数都排在执行队列的后面,代码块执行完之后,结果便会是一大串的setInterval回调函数等待执行,并且这些函数之间没有间隔,直到全部完成。所以,浏览器倾向于的当没有更多interval的处理函数在排队时再将下一个处理函数排到队尾(这是由于间隔的问题)。

我们能够发现,当第三个setInterval回调函数被触发时,之前的setInterval回调函数仍在执行。这就说明了一个很重要的事实:setInterval不会考虑当前正在执行什么,而把所有的堵塞的函数排到队列尾部。这意味着两次setInterval回调函数之间的时间间隔会被牺牲掉(缩减)。

最后,当第二个setInterval回调函数执行完毕后,我们可以看到没有任何程序等待JavaScript引擎执行了。这就意味着浏览器现在在等待一个新的异步事件的发生。在50ms时一个新的setInterval回调函数再次被触发,这时,没有任何的执行块阻塞它的执行了。所以它会立刻被执行。让我们用一个例子来阐明setTimeout和setInterval之间的区别:

setTimeout(function(){ 
/* Some long block of code... */ 
setTimeout(arguments.callee, 10); 
 }, 10); 
  
 setInterval(function(){ 
/* Some long block of code... */ 
 }, 10); setTimeout(function(){ 
/* Some long block of code... */ 
setTimeout(arguments.callee, 10); 
 }, 10); 
  
 setInterval(function(){ 
/* Some long block of code... */ 
 }, 10);

这两句代码乍一看没什么差别,但是它们是不同的。setTimeout回调函数的执行和上一次执行之间的间隔至少有10ms(可能会更多,但不会少于10ms),而setInterval的回调函数将尝试每隔10ms执行一次,不论上次是否执行完毕。

总结

◆JavaScript引擎是单线程的,强制所有的异步事件排队等待执行;

◆setTimeout和 setInterval在执行异步代码的时候有着根本的不同;

◆如果一个计时器被阻塞而不能立即执行,它将延迟执行直到下一次可能执行的时间点才被执行(比期望的时间间隔要长些);

◆如果setInterval回调函数的执行时间将足够长(比指定的时间间隔长),它们将连续执行并且彼此之间没有时间间隔。

上述这些知识点都是非常重要的。了解了JavaScript引擎是如何工作的,尤其是大量的异步事件(连续)发生时,才能为构建高级应用程序打好基础。

原文作者:John Resig

原文链接: http://ejohn.org/blog/how-javascript-timers-work/

Javascript 相关文章推荐
jquery复选框CHECKBOX全选、反选
Aug 30 Javascript
setTimeout 不断吐食CPU的问题分析
Apr 01 Javascript
IE innerHTML,outerHTML所引起的问题
Jun 04 Javascript
document.getElementById介绍
Sep 13 Javascript
Windows系统下Node.js的简单入门教程
Jun 23 Javascript
jQuery定义插件的方法
Dec 18 Javascript
jquery中validate与form插件提交的方式小结
Mar 26 Javascript
jQuery实现滚动到底部时自动加载更多的方法示例
Feb 18 jQuery
jQuery中复合选择器简单用法示例
Mar 31 jQuery
微信小程序日历组件使用方法详解
Dec 29 Javascript
package.json配置文件构成详解
Aug 27 Javascript
vue打包npm run build时候界面报错的解决
Aug 13 Javascript
onsubmit阻止form表单提交与onclick的相关操作
Sep 03 #Javascript
判断浏览器的javascript版本的代码
Sep 03 #Javascript
Extjs中DisplayField的日期或者数字格式化扩展
Sep 03 #Javascript
JavaScript的类型简单说明
Sep 03 #Javascript
JavaScript类和继承 this属性使用说明
Sep 03 #Javascript
JavaScript类和继承 prototype属性
Sep 03 #Javascript
用Javascript实现Sleep暂停功能代码
Sep 03 #Javascript
You might like
《星际争霸II》全新指挥官斯台特曼现已上线
2020/03/08 星际争霸
php计算给定日期所在周的开始日期和结束日期示例
2017/02/06 PHP
PHP实现表单提交数据的验证处理功能【防SQL注入和XSS攻击等】
2017/07/21 PHP
PHP实现正则表达式分组捕获操作示例
2018/02/03 PHP
Yii2.0框架模型添加/修改/删除数据操作示例
2019/07/18 PHP
Extjs Ajax 乱码问题解决方案
2009/04/15 Javascript
JS操作Cookie写入和读取实例代码
2013/10/20 Javascript
Nodejs从有门道无门菜鸟起飞必看教程
2016/07/20 NodeJs
jQuery网页定位导航特效实现方法
2016/12/19 Javascript
Angular2学习教程之TemplateRef和ViewContainerRef详解
2017/05/25 Javascript
深究AngularJS中ng-drag、ng-drop的用法
2017/06/12 Javascript
详解vue2 $watch要注意的问题
2017/09/08 Javascript
Vue2.0学习之详解Vue 组件及父子组件通信
2017/12/12 Javascript
基于vue中css预加载使用sass的配置方式详解
2018/03/13 Javascript
微信小程序学习笔记之获取位置信息操作图文详解
2019/03/29 Javascript
Node.js对MongoDB进行增删改查操作的实例代码
2019/04/18 Javascript
微信小程序使用canvas自适应屏幕画海报并保存图片功能
2019/07/25 Javascript
微信小程序(订阅消息)功能
2019/10/25 Javascript
JS document文档的简单操作完整示例
2020/01/13 Javascript
[05:26]TI10典藏宝瓶套装外观展示
2020/07/03 DOTA
[43:03]完美世界DOTA2联赛PWL S2 PXG vs Magma 第二场 11.21
2020/11/24 DOTA
[01:06:30]DOTA2-DPC中国联赛定级赛 Phoenix vs DLG BO3第二场 1月9日
2021/03/11 DOTA
Python调用C/C++动态链接库的方法详解
2014/07/22 Python
python中正则表达式的使用详解
2014/10/17 Python
python实现自动更换ip的方法
2015/05/05 Python
Python使用PIL模块生成随机验证码
2017/11/21 Python
Python排序搜索基本算法之选择排序实例分析
2017/12/09 Python
Django多数据库联用实现方法解析
2020/11/12 Python
Python中logging日志的四个等级和使用
2020/11/17 Python
什么是静态路由,其特点是什么?什么是动态路由,其特点是什么?
2013/07/26 面试题
往来会计岗位职责
2013/12/19 职场文书
高级护理专业毕业生推荐信
2013/12/25 职场文书
企业内控岗位的职责
2014/02/07 职场文书
党员领导干部民主生活会批评与自我批评发言
2014/09/28 职场文书
公司领导班子对照检查存在问题整改措施
2014/10/02 职场文书
构建和谐校园倡议书
2015/01/19 职场文书