要理解JavaScript的定时机制,就要知道JavaScript的运行机制。

首先声明,JavaScript是单线程运行(JavaScript引擎线程)事件驱动。

一、浏览器中有多个线程

一款浏览器中包含的最基本的线程:

1、JavaScript引擎线程。

2、定时器线程,setInterval和setTimeout会触发这个线程。

3、浏览器事件触发线程,这个线程会触发onclick、onmousemove和其它浏览器事件。

4、界面渲染线程,负责渲染浏览器界面HTML元素。注意:在JavaScript引擎运行脚本期间,界面渲染线程都是处于挂起状态的。也就是说当使用JavaScript对界面中的节点进行操作时,并不会立即体现出来,要等到JavaScript引擎线程空闲时,才会体现出来。(这个最后说)

5、HTTP请求线程(Ajax请求也在其中)。

以上这些线程在浏览器内核的控制下,相互配合,完成工作(具体我也不知道)。

二、任务队列

我们知道JavaScript是单线程的,所有JavaScript代码都在JavaScript引擎线程中运行。阮一峰老师的文章中叫这个线程为主线程,是一个执行栈。(以下内容也主要是根据阮一峰老师的文章理解总结。)

这些JavaScript代码我们可以把他们看成一个个的任务,这些任务有同步任务和异步任务之分。同步任务(比如变量赋值语句,alert语句,函数声明语句等等)直接在主线程上按顺序执行,异步任务(比如浏览器事件触发线程触发的各种各样的事件,使用Ajax返回的服务器响应等)按照时间先后顺序在任务队列(也可以叫做事件队列、消息队列)中排队,等待被执行。只要主线程上的任务执行完了,就会去检查任务队列,看有没有排队等待的任务,有就让排队的任务进入主线程执行。

比如下面的例子:

 <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>定时机制</title> <style type="text/css">
body{
margin: 0;
padding: 0;
position: relative;
height: 600px;
} #test{
height: 30px;
width: 30px;
position: absolute;
left: 0;
top: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div id="test"> </div> <script>
var pro = document.getElementById('test');
pro.onclick = function() {
alert('我没有立即被执行。');
};
function test() {
a = new Date();
var b=0;
for(var i=0;i<3000000000;i++) {
b++;
}
c = new Date();
console.log(c-a);
} test();
</script>
</body>
</html>

在这个例子中test()函数执行完大概要8~9秒,所以当我们打开这个页面,在8秒之前点击粉色方块,不会立即弹出提示框,而要等到8秒之后才弹出,而且8秒之前点击几次粉色框,8秒之后就弹出几次。

我们打开这个页面时,主线程先声明函数test,再声明变量pro,然后把p节点赋值给pro,然后给p节点添加click事件,并指定回调函数(挂起),然后调用test函数,执行其中的代码。在test函数中的代码执行期间,我们点击了p节点,浏览器事件触发线程检测到这个事件,就把这个事件放在了任务队列中,以便主线程上的任务(这里是test函数)执行完后,检查任务队列时发现这个事件并执行相应的回调函数。如果我们多次点击,这些多次触发的事件就按触发时间的先后在任务队列中排队(可以再给另外一个元素添加点击事件,交替点击不同的元素来验证)。

下面是阮老师总结的任务的运行机制:

异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)

1、所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

2、主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

3、一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

4、主线程不断重复上面的第三步。

三、事件和回调函数

我们在给DOM元素指定事件时,都会指定一个回调函数,以便事件真的发生时执行相应的代码。

主线程中事件的回调函数会被挂起,如果任务队列中有正在排队的相应的事件,当主线程检测到时就会执行相应的回调函数。我们也可以说主线程执行异步任务,就是在执行相应的回调函数。

四、事件循环

主线程检查任务队列中事件的过程是循环不断的,因此我们可以画一个事件循环的图:

上图中主线程产生堆和执行栈,栈中的任务执行完毕后,主线程检查任务队列中由其他线程传入的发生过的事件,检测到排在最前面的事件,就从挂起的回调函数中找出与该事件对应的回调函数,然后在执行栈中执行,这个过程一直重复。

五、定时器

结合以上知识,下面探讨JavaScript中的定时器:setTimeout()和setInterval()。

setTimeout(func, t)是超时调用,间隔一段时间后调用函数。这个过程在事件循环中的过程如下(我的理解):

主线程执行完setTimeout(func, t);语句后,把回调函数func挂起,同时定时器线程开始计时,当计时等于t时,相当于发生了一个事件,这个事件传入任务队列(结束计时,只计时一次),当主线程中的任务执行完后,主线程检查任务队列发现了这个事件,就执行挂起的回调函数func。我们指定的时间间隔t只是参考值,真正的时间间隔取决于执行完setTimeout(func, t);语句后的代码所花费的时间,而且是只大不小。(即使我们把t设为0,也要经历这个过程)。

setInterval(func, t)是间歇调用,每隔一段时间后调用函数。这个过程在事件循环中的过程与上面的类似,但又有所不同。

setTimeout()是经过时间t后定时器线程在任务队列中添加一个事件(注意是一个),而setInterval()是每经过时间t(一直在计时,除非清除间歇调用)后定时器线程在任务队列中添加一个事件,而不管之前添加的事件有没有被主线程检测到并执行。(实际上浏览器是比较智能的,浏览器在处理setInterval的时候,如果发现已任务队列中已经有排队的同一ID的setInterval的间歇调用事件,就直接把新来的事件 Kill 掉。也就是说任务队列中一次只能存在一个来自同一ID的间歇调用的事件。)

举个例子,假如执行完setInterval(func, t);后的代码要花费2t的时间,当2t时间过后,主线程从任务队列中检测到定时器线程传入的第一个间歇调用事件,func开始执行。当第一次的func执行完毕后,第二次的间歇调用事件早已传入任务队列,主线程马上检测到第二次的间歇调用事件,func函数又立即执行。这种情况下func函数的两次执行是连续发生的,中间没有时间间隔。

下面是个例子:

    function test() {
a = new Date();
var b=0;
for(var i=0;i<3000000000;i++) {
b++;
}
c = new Date();
console.log(c-a);
}
function test2() {
var d = new Date().valueOf();
//var e = d-a;
console.log('我被调用的时刻是:'+d+'ms');
//alert(1);
}
setInterval(test2,3000); test();

结果:

为什么8.6秒过后没有输出两个一样的时刻,原因在上面的内容中可以找到。

执行例子中的for循环花费了8601ms,在执行for循环的过程中队列中只有一个间歇调用事件在排队(原因如上所述),当8601ms过后,第一个间歇调用事件进入主线程,对于这个例子来说此时任务队列空了,可以再次传入间歇调用事件了,所以1477462632228ms这个时刻第二次间歇调用事件(实际上应该是第三次)传入任务队列,由于主线程的执行栈已经空了,所以主线程立即把对应的回调函数拿来执行,第二次调用与第一次调用之间仅仅间隔了320ms(其实8601+320=8920,差不多就等于9秒了)。我们看到第三次调用已经恢复正常了,因为此时主线程中已经没有其他代码了,只有一个任务,就是隔一段时间执行一次间歇调用的回调函数。

用setTimeout()实现间歇调用的例子:

    function test() {
a = new Date();
var b=0;
for(var i=0;i<3000000000;i++) {
b++;
}
c = new Date();
console.log(c-a);
} function test2(){
var d = new Date().valueOf();
console.log('我被调用的时刻是:'+d+'ms');
setTimeout(test2,3000);
}
setTimeout(test2,3000);
test();

结果:

每两次调用的时间间隔基本上是相同。想想为什么?

再看一个例子:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flex布局练习</title> <style type="text/css">
body{
margin: 0;
padding: 0;
position: relative;
height: 600px;
} #test{
height: 30px;
width: 30px;
position: absolute;
left: 0;
top: 100px;
background-color: pink;
}
</style>
</head>
<body>
<div id="test"> </div> <script>
var p = document.createElement('p');
p.style.width = '50px';
p.style.height = '50px';
p.style.border = '1px solid black'; document.body.appendChild(p); alert('ok'); </script>
</body>
</html>

这个例子的结果是提示框先弹出,然后黑色边框的p元素才出现在页面中。原因很简单,就一句话:

在JavaScript引擎运行脚本期间,界面渲染线程都是处于挂起状态的。也就是说当使用JavaScript对界面中的节点进行操作时,并不会立即体现出来,要等到JavaScript引擎线程空闲时,才会体现出来。

以上就是我对JavaScript定时机制的理解及总结,如有错误,希望看到的大神指正。

参考文献:

1、JavaScript 运行机制详解:再谈Event Loop   http://www.ruanyifeng.com/blog/2014/10/event-loop.html

2、一家之言:说说 JavaScript 计时器的工作原理   http://www.daqianduan.com/1112.html

3、JavaScript可否多线程? 深入理解JavaScript定时机制   http://www.phpv.net/html/1700.html

关于JavaScript定时机制的总结的更多相关文章

  1. JavaScript可否多线程? 深入理解JavaScript定时机制

    JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不少人都深有同感, 例如 setTimeout( ...

  2. 深入理解JavaScript定时机制和定时器注意问题

    容易欺骗别人感情的JavaScript定时器 JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不 ...

  3. 阅读《深入理解JavaScript定时机制》

    鸟哥的这篇<深入理解JavaScript定时机制>从javascript线程角度分析了setTimeout和setInterval两个定时触发器的实现原理. 看完的体验就是主要要记住两点: ...

  4. JavaScript可否多线程? 深入理解JavaScript定时机制(转载)

    说明:最近写 js 时需要用setinterval函数做定时操作,谁知道,刚开始后运行完好,但一段时间后他就抽风了,定时任务运行的时间间隔越来越短,频率加快,这是一个完全不能容忍的问题,带着一个可以出 ...

  5. JavaScript定时机制setTimeout与setInterval研究

    JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不少人都深有同感, 例如 setTimeout( ...

  6. 转:JavaScript定时机制、以及浏览器渲染机制 浅谈

    昨晚,朋友拿了一道题问我: a.onclick = function(){ setTimeout(function() { //do something ... },0); }; //~~~ 我只知道 ...

  7. JavaScript定时机制、以及浏览器渲染机制 浅谈

    昨晚,朋友拿了一道题问我: a.onclick = function(){ setTimeout(function() { //do something ... },0); }; JavaScript ...

  8. 深入理解JavaScript定时机制

    容易欺骗别人感情的JavaScript定时器 JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不 ...

  9. 从setTimeout谈JavaScript运行机制

    从setTimeout说起 众所周知,JavaScript是单线程的编程,什么是单线程,就是说同一时间JavaScript只能执行一段代码,如果这段代码要执行很长时间,那么之后的代码只能尽情地等待它执 ...

随机推荐

  1. Think in 递归

    网上写递归的文章可以用汗牛充栋来形容了,大多数都非常清晰而又细致的角度上讲解了递归的概念,原理等等.以前学生的时候,递归可以说一直是我的某种死穴,原理,细节我都懂,但是不管是在如何运用或者如何试试算法 ...

  2. Struts2登录小例子

    前面实现了一个数据显示的例子,下面我来实现以下使用Struts2登录 首先是配置不用过多解释 注意名字要和类名保持一致 因为实现的是action这个方法所以需要用action.log来跳转到类里面 解 ...

  3. SqlServer游标简介

    游标实例:             Declare MyCusror Cursor Scroll For Select * From Master_Goods Order By GoodsID Ope ...

  4. 程序员的经济学系列——你不可不知的生存智慧——第一篇:小X是要成为IT精英的男人!

    21世纪,不懂经济学就是耍流氓!如何才能生活得更好?作为程序员你一定也思考过这个问题.今天我们就来从经济学中寻找这问题的答案吧! 一·PPF与机会成本 1.PPF综述 首先为大家介绍第一个最简单的经济 ...

  5. Mac下配置Apache服务

    这篇文章主要是针对Mac用户,第一次搭建本地开发环境的同学,已经搭建过的同学可以忽略. Mac自带的Apache还是XAMPP? That is a question. 其实自带的apache也够用了 ...

  6. 在浏览器的背后(二) —— HTML语言的语法解析

    当你看到这篇文章意味着我辜负了@教主的殷切期望周末木有去约会,以及苏老师@我思故我在北京鼓楼的落井下石成功了…… 本文demo powered by 已经结婚的@老赵的不再维护的wind.js 物是人 ...

  7. LInux MySQL 数据库 的一些操作

    数据库安装: ………… 创建数据库连接新用户: 1.登录mysql #mysql -u root -p 2.新增用户 insert into mysql.user(Host,User,Password ...

  8. 2013 duilib入门简明教程 -- 结合win32和MFC (16)

        虽然duilib自带在MFC中使用duilib的Demo,但只是MFC窗口和duilib窗口不重叠的情况.如果要在MFC窗口中嵌入duilib控件,或者在duilib控件中嵌入MFC的控件的话 ...

  9. Android知识——ViewHolder的作用与用法

    ViewHolder通常出现在适配器里,为的是listview滚动的时候快速设置值,而不必每次都重新创建很多对象,从而提升性能.在android开发中Listview是一个很重要的组件,它以列表的形式 ...

  10. angularjs揭秘

    angularjs揭秘