深入理解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 1.0.4 - New Wave Javascript(js源文件)
Jan 15 Javascript
js RuntimeObject() 获取ie里面自定义函数或者属性的集合
Nov 23 Javascript
整理一些JavaScript的IE和火狐的兼容性注意事项
Mar 17 Javascript
jQuery数组处理方法汇总
Jun 20 Javascript
jquery自动填充勾选框即把勾选框打上true
Mar 24 Javascript
JQuery判断radio(单选框)是否选中和获取选中值方法总结
Apr 15 Javascript
Bootstrap入门教程一Hello Bootstrap初识
Mar 02 Javascript
正则 js分转元带千分符号详解
Mar 08 Javascript
ES6学习笔记之Set和Map数据结构详解
Apr 07 Javascript
vuex actions传递多参数的处理方法
Sep 18 Javascript
ES6扩展运算符和rest运算符用法实例分析
May 23 Javascript
js实现鼠标拖曳效果
Dec 30 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破解防盗链图片的一个简单方法
2014/06/07 PHP
PHP那些琐碎的知识点(整理)
2017/05/20 PHP
php利用云片网实现短信验证码功能的示例代码
2017/11/18 PHP
PHP模版引擎原理、定义与用法实例
2019/03/29 PHP
Javascript客户端脚本的设计和应用
2006/08/21 Javascript
8款非常棒的响应式jQuery 幻灯片插件推荐
2012/02/02 Javascript
jQuery学习笔记 操作jQuery对象 属性处理
2012/09/19 Javascript
js去字符串前后空格5种实现方法及比较
2013/04/03 Javascript
tangram框架响应式加载图片方法
2013/11/21 Javascript
table行随鼠标移动变色示例
2014/05/07 Javascript
WebApi+Bootstrap+KnockoutJs打造单页面程序
2016/05/16 Javascript
学习JavaScript图片预加载模块
2016/11/07 Javascript
浅谈JavaScript中的apply/call/bind和this的使用
2017/02/26 Javascript
vue2里面ref的具体使用方法
2017/10/27 Javascript
vue+vuex+axios实现登录、注册页权限拦截
2018/03/09 Javascript
自己动手封装一个React Native多级联动
2018/09/19 Javascript
jquery获取并修改触发事件的DOM元素示例【基于target 属性】
2019/10/10 jQuery
Vue数据双向绑定底层实现原理
2019/11/22 Javascript
[01:50]2014DOTA2西雅图邀请赛 专访欢乐周宝龙
2014/07/08 DOTA
[46:21]Liquid vs LGD 2018国际邀请赛淘汰赛BO3 第一场 8.23
2018/08/24 DOTA
python实现日常记账本小程序
2018/03/10 Python
对python使用telnet实现弱密码登录的方法详解
2019/01/26 Python
python实现两个经纬度点之间的距离和方位角的方法
2019/07/05 Python
Pytorch反向求导更新网络参数的方法
2019/08/17 Python
Python如何实现动态数组
2019/11/02 Python
python3实现网页版raspberry pi(树莓派)小车控制
2020/02/12 Python
基于python实现把json数据转换成Excel表格
2020/05/07 Python
python使用hdfs3模块对hdfs进行操作详解
2020/06/06 Python
实列教程 一款基于jquery和css3的响应式二级导航菜单
2014/11/13 HTML / CSS
Pure Collection美国官网:来自英国羊绒专家的奢华羊绒
2017/11/19 全球购物
简述进程的启动、终止的方式以及如何进行进程的查看
2013/07/12 面试题
药学专业学生的自我评价分享
2014/02/06 职场文书
学校募捐倡议书
2014/05/14 职场文书
2015幼儿园庆元旦活动方案
2014/12/09 职场文书
大国崛起观后感
2015/06/02 职场文书
2016年度员工工作表现评语
2015/12/02 职场文书