深入理解JavaScript定时机制


Posted in Javascript onOctober 27, 2016

本文介绍了JavaScript定时机制,要理解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定时机制

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

五、定时器

结合以上知识,下面探讨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();

结果:

深入理解JavaScript定时机制

为什么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();

 结果:

深入理解JavaScript定时机制

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

再看一个例子:

<!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定时机制的理解及总结,如有错误,希望看到的大神指正。也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jquery获取table中的某行全部td的内容方法
Mar 08 Javascript
解决window.opener=null;window.close(),只支持IE6不支持IE7,IE8的问题
Jan 14 Javascript
JavaScript中实现Map的示例代码
Sep 09 Javascript
JS鼠标拖拽实例分析
Nov 23 Javascript
30分钟快速掌握Bootstrap框架
May 24 Javascript
Bootstrap基本插件学习笔记之按钮(21)
Dec 08 Javascript
EditPlus 正则表达式 实战(3)
Dec 15 Javascript
jQuery实现可拖拽3D万花筒旋转特效
Jan 03 Javascript
vue中如何让子组件修改父组件数据
Jun 14 Javascript
vue项目中跳转到外部链接的实例讲解
Sep 20 Javascript
详解vue-cli+element-ui树形表格(多级表格折腾小计)
Apr 17 Javascript
vue3使用vue-count-to组件的实现
Dec 25 Vue.js
解析javascript图片懒加载与预加载的分析总结
Oct 27 #Javascript
js实现加载更多功能实例
Oct 27 #Javascript
Vue.js一个文件对应一个组件实践
Oct 27 #Javascript
JavaScript实现类似拉勾网的鼠标移入移出效果
Oct 27 #Javascript
node.js文件上传处理示例
Oct 27 #Javascript
Vue.js表单控件实践
Oct 27 #Javascript
vue实现可增删查改的成绩单
Oct 27 #Javascript
You might like
smarty基础之拼接字符串的详解
2013/06/18 PHP
PHP错误Parse error: syntax error, unexpected end of file in test.php on line 12解决方法
2014/06/23 PHP
php中print(),print_r(),echo()的区别详解
2014/12/01 PHP
PHP基于方差和标准差计算学生成绩的稳定性示例
2017/07/04 PHP
浅谈Laravel POST,PUT,PATCH 路由的区别
2019/10/15 PHP
JQuery textlimit 显示用户输入的字符数 限制用户输入的字符数
2009/05/14 Javascript
js拼接html注意问题示例探讨
2014/07/14 Javascript
javascript关于继承的用法汇总
2014/12/20 Javascript
利用JQuery直接调用asp.net后台的简单方法
2016/10/27 Javascript
鼠标经过出现气泡框的简单实例
2017/03/17 Javascript
js实现一个简单的数字时钟效果
2017/03/29 Javascript
Vue项目中最新用到的一些实用小技巧
2018/11/06 Javascript
从零开始实现Vue简单的Toast插件
2018/12/03 Javascript
解决vue-router路由拦截造成死循环问题
2020/08/05 Javascript
[01:23:59]2018DOTA2亚洲邀请赛 4.1 小组赛 B组 VP vs Secret
2018/04/03 DOTA
[01:11:27]2018DOTA2亚洲邀请赛小组赛 A组加赛 Newbee vs Optic
2018/04/03 DOTA
python中from module import * 的一个坑
2014/07/20 Python
python 中的int()函数怎么用
2017/10/17 Python
python获取当前目录路径和上级路径的实例
2018/04/26 Python
Python sorted函数详解(高级篇)
2018/09/18 Python
python微信聊天机器人改进版(定时或触发抓取天气预报、励志语录等,向好友推送)
2019/04/25 Python
Python OrderedDict字典排序方法详解
2020/05/21 Python
Python 代码调试技巧示例代码
2020/08/11 Python
python3环境搭建过程(利用Anaconda+pycharm)完整版
2020/08/19 Python
如何通过python检查文件是否被占用
2020/12/18 Python
一款基于css3的动画按钮代码教程
2014/11/23 HTML / CSS
土耳其时尚购物网站:Morhipo
2017/09/04 全球购物
Lands’ End英国官方网站:高质量男女服装
2017/10/07 全球购物
优秀公益广告词大全
2014/03/19 职场文书
环保倡议书400字
2014/05/15 职场文书
中学生爱国演讲稿
2014/09/05 职场文书
毕业生银行实习自我鉴定
2014/10/14 职场文书
结婚堵门保证书
2015/05/08 职场文书
房产电话营销开场白
2015/05/29 职场文书
公司管理建议书
2015/09/14 职场文书
Vue Element plus使用方法梳理
2022/12/24 Vue.js