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 相关文章推荐
ie8 不支持new Date(2012-11-10)问题的解决方法
Jul 31 Javascript
jquery 缓存问题的几个解决方法
Nov 11 Javascript
JQuery遍历DOM节点的方法
Jun 11 Javascript
基于JavaScript实现窗口拖动效果
Jan 18 Javascript
深入理解JavaScript中的for循环
Feb 07 Javascript
深入理解Angular.JS中的Scope继承
Jun 04 Javascript
初识 Vue.js 中的 *.Vue文件
Nov 22 Javascript
图文介绍Vue父组件向子组件传值
Feb 17 Javascript
详解vue 数据传递的方法
Apr 19 Javascript
详解Vue微信授权登录前后端分离较为优雅的解决方案
Jun 29 Javascript
跟混乱的页面弹窗说再见
Apr 11 Javascript
微信小程序如何使用canvas二维码保存至手机相册
Jul 15 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
php下防止单引号,双引号在接受页面转义的设置方法
2008/09/25 PHP
php 一元分词算法
2009/11/30 PHP
PHP无限分类(树形类)
2013/09/28 PHP
PHP防止post重复提交数据的简单例子
2014/06/07 PHP
Laravel中注册Facades的步骤详解
2016/03/16 PHP
php中时间函数date及常用的时间计算
2017/05/12 PHP
js确定对象类型方法
2012/03/30 Javascript
js操作CheckBoxList实现全选/反选(在客服端完成)
2013/02/02 Javascript
绑定回车enter事件代码
2014/05/18 Javascript
复杂的javascript窗口分帧解析
2016/02/19 Javascript
AngularJS模块详解及示例代码
2016/08/17 Javascript
Angular懒加载机制刷新后无法回退的快速解决方法
2016/08/30 Javascript
JavaScript九九乘法口诀表的简单实现
2016/10/04 Javascript
jquery网页日历显示控件calendar3.1使用详解
2016/11/24 Javascript
微信小程序实现折叠面板
2018/01/31 Javascript
解决vuex刷新状态初始化的方法实现
2019/08/15 Javascript
[03:00]DOTA2-DPC中国联赛1月18日Recap集锦
2021/03/11 DOTA
python 正则表达式 概述及常用字符
2009/05/04 Python
初步剖析C语言编程中的结构体
2016/01/16 Python
一份python入门应该看的学习资料
2018/04/11 Python
给我一面国旗 python帮你实现
2019/09/30 Python
Python 实现取多维数组第n维的前几位
2019/11/26 Python
布隆过滤器的概述及Python实现方法
2019/12/08 Python
pytorch中的自定义数据处理详解
2020/01/06 Python
TensorFlow 输出checkpoint 中的变量名与变量值方式
2020/02/11 Python
Python如何实现爬取B站视频
2020/05/20 Python
html5 Canvas画图教程(2)—画直线与设置线条的样式如颜色/端点/交汇点
2013/01/09 HTML / CSS
AmazeUI 网格的实现示例
2020/08/13 HTML / CSS
Bootstrap File Input文件上传组件
2020/12/01 HTML / CSS
美国儿童珠宝在线零售商:Loveivy
2019/05/22 全球购物
学生自我鉴定
2013/12/18 职场文书
服装设计专业自荐书范文
2013/12/30 职场文书
副董事长岗位职责
2014/04/02 职场文书
学校周年庆活动方案
2014/08/22 职场文书
oracle DGMGRL ORA-16603报错的解决方法(DG Broker)
2021/04/06 Oracle
BCL经典机 SONY ICF-5900W电路分析
2022/04/24 无线电