深入理解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 相关文章推荐
JavaScript var声明变量背后的原理示例解析
Oct 12 Javascript
Javascript中获取对象的原型对象的方法小结
Feb 25 Javascript
实例剖析AngularJS框架中数据的双向绑定运用
Mar 04 Javascript
jquery输入数字随机抽奖特效的简单实现代码
Jun 10 Javascript
js的各种排序算法实现(总结)
Jul 23 Javascript
Jquery AJAX POST与GET之间的区别详细介绍
Oct 17 Javascript
JavaScript实现清空(重置)文件类型INPUT元素值的方法
Nov 17 Javascript
利用jquery正则表达式在页面验证url网址输入是否正确
Apr 04 jQuery
AngularJS $http模块POST请求实现
Apr 08 Javascript
浅谈angularJS的$watch失效问题的解决方案
Aug 11 Javascript
Vue Cli与BootStrap结合实现表格分页功能
Aug 18 Javascript
封装Vue Element的table表格组件的示例详解
Aug 19 Javascript
解析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
PHP+Mysql树型结构(无限分类)数据库设计的2种方式实例
2014/07/15 PHP
php递归法读取目录及文件的方法
2015/01/30 PHP
Yii2单元测试用法示例
2016/11/12 PHP
Laravel 5.4前后台分离,通过不同的二级域名访问方法
2019/10/13 PHP
Jquery 类网页微信二维码图块滚动效果具体实现
2013/10/14 Javascript
js中substring和substr的定义和用法
2014/05/05 Javascript
谷歌浏览器不支持showModalDialog模态对话框的解决方法
2014/09/22 Javascript
jQuery晃动层特效实现方法
2015/03/09 Javascript
浅谈window对象的scrollBy()方法
2015/07/15 Javascript
详解 javascript中offsetleft属性的用法
2015/11/11 Javascript
总结JavaScript中布尔操作符||与&amp;&amp;的使用技巧
2015/11/17 Javascript
javascript HTML5文件上传FileReader API
2020/03/27 Javascript
在Mac OS上安装使用Node.js的项目自动化构建工具Gulp
2016/06/18 Javascript
jQuery 特性操作详解及实例代码
2016/09/29 Javascript
微信小程序日期时间选择器使用方法
2018/02/01 Javascript
D3.js实现拓扑图的示例代码
2018/06/30 Javascript
vue-router 手势滑动触发返回功能
2018/09/30 Javascript
Angular6 Filter实现页面搜索的示例代码
2018/12/02 Javascript
vue-router 前端路由之路由传值的方式详解
2019/04/30 Javascript
[03:05]《我与DAC》之xiao8:DAC与BG
2018/03/27 DOTA
Python序列操作之进阶篇
2016/12/08 Python
Python 装饰器使用详解
2017/07/29 Python
对python中使用requests模块参数编码的不同处理方法
2018/05/18 Python
python一键去抖音视频水印工具
2018/09/14 Python
python TKinter获取文本框内容的方法
2018/10/11 Python
Jupyter notebook快速入门教程(推荐)
2020/05/18 Python
Java和Javasciprt的区别
2012/09/02 面试题
2014升学宴答谢词
2014/01/26 职场文书
水污染治理工程专业自荐信
2014/06/21 职场文书
清明节寄语2015
2015/03/23 职场文书
大学生暑期实践报告
2015/07/13 职场文书
安全生产培训心得体会
2016/01/18 职场文书
Python自动化爬取天眼查数据的实现
2021/06/15 Python
Python-OpenCV教程之图像的位运算详解
2021/06/21 Python
MySQL如何解决幻读问题
2021/08/07 MySQL
vue配置型表格基于el-table拓展之table-plus组件
2022/04/12 Vue.js