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 相关文章推荐
下载网站打开页面后间隔多少时间才显示下载链接地址的代码
Apr 25 Javascript
String.prototype实现的一些javascript函数介绍
Nov 22 Javascript
js 判断js函数、变量是否存在的简单示例代码
Mar 04 Javascript
node.js中的fs.chmodSync方法使用说明
Dec 18 Javascript
微信小程序三级联动地址选择器的实例代码
Jul 12 Javascript
JS操作时间 - UNIX时间戳的简单介绍(必看篇)
Aug 16 Javascript
解读vue生成的文件目录结构及说明
Nov 27 Javascript
RequireJS用法简单示例
Aug 20 Javascript
JS使用对象的defineProperty进行变量监控操作示例
Feb 02 Javascript
ES6入门教程之变量的解构赋值详解
Apr 13 Javascript
我要点爆”微信小程序云开发之项目建立与我的页面功能实现
May 26 Javascript
让mocha支持ES6模块的方法实现
Jan 14 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
Linux fgetcsv取得的数组元素为空字符串的解决方法
2011/11/25 PHP
php+js实现百度地图多点标注的方法
2016/11/30 PHP
父子窗体间传递JSON格式的数据的代码
2010/12/25 Javascript
javascript动态加载实现方法一
2012/08/22 Javascript
JavaScript将Table导出到Excel实现思路及代码
2013/03/13 Javascript
js用Date对象的setDate()函数对日期进行加减操作
2014/09/18 Javascript
javascript使用smipleChart实现简单图表
2015/01/02 Javascript
js实现简单div拖拽功能实例
2015/05/12 Javascript
使用JavaScript实现表格编辑器(实例讲解)
2017/08/02 Javascript
关于Google发布的JavaScript代码规范你要知道哪些
2018/04/04 Javascript
Vue.js中关于侦听器(watch)的高级用法示例
2018/05/02 Javascript
Vue CLI3搭建的项目中路径相关问题的解决
2018/09/17 Javascript
深入探讨JavaScript的最基本部分之执行上下文
2019/02/12 Javascript
vuex vue简单使用知识点总结
2019/08/29 Javascript
js 判断当前时间是否处于某个一个时间段内
2019/09/19 Javascript
[59:32]Liquid vs Fnatic 2019国际邀请赛淘汰赛败者组BO1 8.20.mp4
2020/07/19 DOTA
布同自制Python函数帮助查询小工具
2011/03/13 Python
Python中list列表的一些进阶使用方法介绍
2015/08/15 Python
pymongo中聚合查询的使用方法
2019/03/22 Python
使用Python创建简单的HTTP服务器的方法步骤
2019/04/26 Python
Flask项目中实现短信验证码和邮箱验证码功能
2019/12/05 Python
python Tensor和Array对比分析
2020/01/08 Python
Tensorflow 使用pb文件保存(恢复)模型计算图和参数实例详解
2020/02/11 Python
python能开发游戏吗
2020/06/11 Python
Python中免验证跳转到内容页的实例代码
2020/10/23 Python
临床医学专业个人的自我评价
2013/09/27 职场文书
家长对孩子评语
2014/01/30 职场文书
职业生涯规划书怎么写?
2014/09/14 职场文书
社区元宵节活动总结
2015/02/06 职场文书
建议书的格式及范文
2015/09/14 职场文书
演讲开头怎么书写?
2019/08/06 职场文书
浅谈MySQL 亿级数据分页的优化
2021/06/15 MySQL
Node实现搜索框进行模糊查询
2021/06/28 Javascript
Mysql分析设计表主键为何不用uuid
2022/03/31 MySQL
基于PyQt5制作一个群发邮件工具
2022/04/08 Python
Win11快速关闭所有广告推荐
2022/04/19 数码科技