深入理解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开发包大全整理
Dec 22 Javascript
JQuery对checkbox操作 (循环获取)
May 20 Javascript
js实现图片轮换效果代码
Apr 16 Javascript
Jquery解析json数据详解
Dec 26 Javascript
原生javascript实现Tab选项卡切换功能
Jan 12 Javascript
解决js图片加载时出现404的问题
Nov 30 Javascript
基于jQuery插件实现点击小图显示大图效果
May 11 Javascript
详谈vue+webpack解决css引用图片打包后找不到资源文件的问题
Mar 06 Javascript
微信小程序与后台PHP交互的方法实例分析
Dec 10 Javascript
JavaScript数据结构与算法之二叉树实现查找最小值、最大值、给定值算法示例
Mar 01 Javascript
vue中更改数组中属性,在页面中不生效的解决方法
Oct 30 Javascript
JavaScript Date对象功能与用法学习记录
Apr 28 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数组函数序列 之array_count_values() 统计数组中所有值出现的次数函数
2011/10/29 PHP
PHP自定义函数实现assign()数组分配到模板及extract()变量分配到模板功能示例
2018/05/23 PHP
Yii redis集合的基本使用教程
2020/06/14 PHP
如何在Laravel之外使用illuminate组件详解
2020/09/20 PHP
远离JS灾难css灾难之 js私有函数和css选择器作为容器
2011/12/11 Javascript
JS对象转换为Jquery对象示例
2014/01/26 Javascript
node.js中的http.response.end方法使用说明
2014/12/14 Javascript
jQuery中:enabled选择器用法实例
2015/01/04 Javascript
Nodejs关于gzip/deflate压缩详解
2015/03/04 NodeJs
jquery插件jSignature实现手动签名
2015/05/04 Javascript
JavaScript的removeChild()函数用法详解
2015/12/27 Javascript
使用 stylelint检查CSS_StyleLint
2016/04/28 Javascript
jQuery获取复选框被选中数量及判断选择值的方法详解
2016/05/25 Javascript
简单封装js的dom查询实例代码
2016/07/08 Javascript
浅谈jQuery中的eq()与DOM中element.[]的区别
2016/10/28 Javascript
JS中数组重排序方法
2016/11/11 Javascript
详解为Angular.js内置$http服务添加拦截器的方法
2016/12/20 Javascript
Vue键盘事件用法总结
2017/04/18 Javascript
vue 实现通过手机发送短信验证码注册功能
2018/04/19 Javascript
小程序点击图片实现png转jpg
2019/10/22 Javascript
Python使用sorted对字典的key或value排序
2018/11/15 Python
在python 中split()使用多符号分割的例子
2019/07/15 Python
Python模块汇总(常用第三方库)
2019/10/07 Python
python_mask_array的用法
2020/02/18 Python
Python selenium爬取微博数据代码实例
2020/05/22 Python
Python如何生成xml文件
2020/06/04 Python
荷兰网上药店:Drogisterij.net
2019/09/03 全球购物
公司承诺书格式
2014/05/21 职场文书
工商管理专业毕业生求职信
2014/05/26 职场文书
加入学生会自荐书
2015/03/05 职场文书
食堂采购员岗位职责
2015/04/03 职场文书
离婚被告答辩状
2015/05/22 职场文书
Python数据可视化之用Matplotlib绘制常用图形
2021/06/03 Python
刚学完怎么用Python实现定时任务,转头就跑去撩妹!
2021/06/05 Python
SpringBoot连接MySQL获取数据写后端接口的操作方法
2021/11/02 MySQL
Java 在生活中的 10 大应用
2021/11/02 Java/Android