JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法因为我们开始常常以为调用了就会按既定的方式执行 我想不少人都深有同感 例如 [javascript]
setTimeout( function(){ alert(‘你好!); } );
setInterval( callbackFunction );
setTimeout( function(){ alert(’你好!); } );
setInterval( callbackFunction );
认为setTimeout中的问候方法会立即被执行因为这并不是凭空而说而是JavaScript API文档明确定义第二个参数意义为隔多少毫秒后回调方法就会被执行 这里设成毫秒理所当然就立即被执行了
同理对setInterval的callbackFunction方法每间隔毫秒就立即被执行深信不疑!
但随着JavaScript应用开发经验不断的增加和丰富有一天你发现了一段怪异的代码而百思不得其解:
[javascript]
divonclick = function(){
setTimeout( function(){documentgetElementById(inputField)focus();} );
};
divonclick = function(){
setTimeout( function(){documentgetElementById(inputField)focus();} );
};
既然是毫秒后执行那么还用setTimeout干什么 此刻 坚定的信念已开始动摇
直到最后某一天 你不小心写了一段糟糕的代码:
[javascript]
setTimeout( function(){ while(true){} } );
setTimeout( function(){ alert(‘你好!); } );
setInterval( callbackFunction );
setTimeout( function(){ while(true){} } );
setTimeout( function(){ alert(’你好!); } );
setInterval( callbackFunction );
第一行代码进入了死循环但不久你就会发现第二第三行并不是预料中的事情alert问候未见出现callbacKFunction也杳无音讯!
这时你彻底迷惘了这种情景是难以接受的因为改变长久以来既定的认知去接受新思想的过程是痛苦的但情事实摆在眼前对JavaScript真理的探求并不会因为痛苦而停止下面让我们来展开JavaScript线程和定时器探索之旅!
出现上面所有误区的最主要一个原因是:潜意识中认为JavaScript引擎有多个线程在执行JavaScript的定时器回调函数是异步执行的
而事实上的JavaScript使用了障眼法在多数时候骗过了我们的眼睛这里背光得澄清一个事实:
JavaScript引擎是单线程运行的浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序
JavaScript引擎用单线程运行也是有意义的单线程不必理会线程同步这些复杂的问题问题得到简化
那么单线程的JavaScript引擎是怎么配合浏览器内核处理这些定时器和响应浏览器事件的呢?
下面结合浏览器内核处理方式简单说明
浏览器内核实现允许多个线程异步执行这些线程在内核制控下相互配合以保持同步假如某一浏览器内核的实现至少有三个常驻线程:javascript引擎 线程界面渲染线程浏览器事件触发线程除些以外也有一些执行完就终止的线程如Http请求线程这些异步线程都会产生不同的异步事件下面通过一 个图来阐明单线程的JavaScript引擎与另外那些线程是怎样互动通信的虽然每个浏览器内核实现细节不同但这其中的调用原理都是大同小异
JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法因为我们开始常常以为调用了就会按既定的方式执行 我想不少人都深有同感 例如
由图可看出浏览器中的JavaScript引擎是基于事件驱动的这里的事件可看作是浏览器派给它的各种任务这些任务可以源自 JavaScript引擎当前执行的代码块如调用setTimeout添加一个任务也可来自浏览器内核的其它线程如界面元素鼠标点击事件定时触发 器时间到达通知异步请求状态变更通知等从代码角度看来任务实体就是各种回调函数JavaScript引擎一直等待着任务队列中任务的到来由于单线 程关系这些任务得进行排队一个接着一个被引擎处理 上图tttn表示不同的时间点tn下面对应的小方块代表该时间点的任务假设现在是t时刻引擎运行在t对应的任务方块代码内在这个时间点内我们来描述一下浏览器内核其它线程的状态
t时刻:
GUI渲染线程:
该线程负责渲染浏览器界面HTML元素当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时该线程就会执行本文虽然重点解释 JavaScript定时机制但这时有必要说说渲染线程因为该线程与JavaScript引擎线程是互斥的这容易理解因为 JavaScript脚本是可操纵DOM元素在修改这些元素属性同时渲染界面那么渲染线程前后获得的元素数据就可能不一致了
在JavaScript引擎运行脚本期间浏览器渲染线程都是处于挂起状态的也就是说被“冻结”了
所以在脚本中执行对界面进行更新操作如添加结点删除结点或改变结点的外观等更新并不会立即体现出来这些操作将保存在一个队列中待JavaScript引擎空闲时才有机会渲染出来
GUI事件触发线程:
JavaScript脚本的执行不影响html元素事件的触发在t时间段内首先是用户点击了一个鼠标键点击被浏览器事件触发线程捕捉后形成一个鼠 标点击事件由图可知对于JavaScript引擎线程来说这事件是由其它线程异步传到任务队列尾的由于引擎正在处理t时的任务这个鼠标点击事 件正在等待处理
定时触发线程:
注意这里的浏览器模型定时计数器并不是由JavaScript引擎计数的因为JavaScript引擎是单线程的如果处于阻塞线程状态就计不了时它必须依赖外部来计时并触发定时所以队列中的定时事件也是异步事件
由图可知在这t的时间段内继鼠标点击事件触发后先前已设置的setTimeout定时也到达了此刻对JavaScript引擎来说定时触发线程产生了一个异步定时事件并放到任务队列中 该事件被排到点击事件回调之后等待处理
同理 还是在t时间段内接下来某个setInterval定时器也被添加了由于是间隔定时在t段内连续被触发了两次这两个事件被排到队尾等待处理
可见假如时间段t非常长远大于setInterval的定时间隔那么定时触发线程就会源源不断的产生异步定时事件并放到任务队列尾而不管它们是否 已被处理但一旦t和最先的定时事件前面的任务已处理完这些排列中的定时事件就依次不间断的被执行这是因为对于JavaScript引擎来说在 处理队列中的各任务处理方式都是一样的只是处理的次序不同而已
t过后也就是说当前处理的任务已返回JavaScript引擎会检查任务队列发现当前队列非空就取出t下面对应的任务执行其它时间依此类推由此看来:
如果队列非空引擎就从队列头取出一个任务直到该任务处理完即返回后引擎接着运行下一个任务在任务没返回前队列中的其它任务是没法被执行的
相信您现在已经很清楚JavaScript是否可多线程也了解理解JavaScript定时器运行机制了下面我们来对一些案例进行分析:
案例:setTimeout与setInterval
[javascript]
setTimeout(function(){
/* 代码块 */
setTimeout(argumentscallee );
} );
setInterval(function(){
/*代码块 */
} );
setTimeout(function(){
/* 代码块 */
setTimeout(argumentscallee );
} );
setInterval(function(){
/*代码块 */
} );
这两段代码看一起效果一样其实非也第一段中回调函数内的setTimeout是JavaScript引擎执行后再设置新的setTimeout 定时 假定上一个回调处理完到下一个回调开始处理为一个时间间隔理论两个setTimeout回调执行时间间隔>=ms第二段自 setInterval设置定时后定时触发线程就会源源不断的每隔十秒产生异步定时事件并放到任务队列尾理论上两个setInterval回调执行时 间间隔<=
案例:ajax异步请求是否真的异步?
很多同学朋友搞不清楚既然说JavaScript是单线程运行的那么XMLHttpRequest在连接后是否真的异步?
其实请求确实是异步的不过这请求是由浏览器新开一个线程请求(参见上图)当请求的状态变更时如果先前已设置回调这异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理当任务被处理时JavaScript引擎始终是单线程运行回调函数具体点即还是单线程运行 onreadystatechange所设置的函数