javaScript中定时器的用法和原理

来源:互联网 发布:moment.js 计算时间差 编辑:程序博客网 时间:2024/05/23 21:05

js中的线程和定时器


  • 在js中定时器是一个非常强大但经常会被错误使用的一个特性,如果能够正确使用定时器那么就会给开发人员带来非常多的好处
  • 定时器提供一种让一段代码在一定毫秒之后,再 异步执行的能力,由于js是单线程的(同一时间只能执行一处的javascript代码),定时器提供一种跳过这种限制的方法。

1. 定时器和线程是如何工作的

1.1 设置和清楚定时器
js提供了两种方式,用于创建定时器以及相应两种方法(删除),这些方法都是windows对象上的方法

js中操作定时器的方法 :

方法 格式 描述 setTImeout id=setTimemout(fn,time) 启动一个定时器,在一段时间(time)之后执行传入的回调函数 fn,返回一个定时器id用于clear clearTimeout clearTimeout(id) 如果定时器还没触发,传入id就可以取消该定时器 setinterval id=setinterval(fn,time) 启动一个定时器,在每隔一段时间(time)之后执行传入的回调 函数fn,并且返回一个定时器id涌入clear clearinterval clearinterval(id) 传入间隔定时器标识,即清除该定时器

这里需要提示一下,js中的延迟时间是不能保证的,原因和js的单线程有很大关系

1.2 执行进程中的定时器运行
js中的单线程造成的结果是:异步事件的处理程序,如果用户界面和定时器,在线程中没有代码执行的时候才会执行,也就是说这些程序在执行时必须要排队执行,并且一个处理程序不能中断另一个。例如当一个异步事件触发时(如鼠标单击‘click’,定时器触发,甚至是XMLHttpRequest完成事件),它就会排队,并且当线程空闲时它才执行,实际上每一个浏览器的排队机制是不同的。
一个例子
- 在0毫秒时,启动一个10毫秒的setTimeout以及一个10毫秒的setinterval
- 在6毫秒时,执行鼠标点击触发click
- 在10毫秒时,定时器和间隔定时器触发
- 但是第一个js代码块要执行18毫秒
- 直到这段时间内相继出发了 鼠标单击click时间 setTimeout 以及两次setinterval事件,由于这个时间段内有别的代码正在执行,所以这些时间中的处理程序就不能执行,所以就开始排队
- 直到鼠标单击事件结束 在timeout处理程序执行时(原本我们要在10毫秒执行的),注意在30秒的时候又触发了一次间隔定时器,但是由于之前已经有一个interval代码正在排队,所以这次的处理程序就不会执行,按通俗易懂的话就是等到线程中没有处理程序时,才会将其添加到队伍中,浏览器不会对特定的interval处理程序的多个实例进行排队,
- 在34毫秒,timeout执行结束,队列中的interval处理程序开始执行 由于该程序要执行6毫秒,所以在执行到40毫秒又触发interval时间,进入队列,所以到目前为止进入队列的interval只有10毫秒和40豪秒触发的
- 在42秒开始执行40秒触发的之后 因为运行时间为6毫秒所以 之后没次的interval时间都会在每10毫秒运行
正如我们所看到的interval中的几个处理程序完全被“挤没了”
1.3timeout与interval的区别
乍一看感觉两者并无什么明显区别

<script type="text/javascript">setTimout(function(){  //执行功能  settimeout(repeatMe,10); //重新调用自己})setInterval(function(){  //执行功能},10)  //定义一个Interval定时器,每隔10秒触发一次<script>

在上述代码中,两者功能看似是相同的,实际上是不同的,其实在setTimeout()代码中,要在前一个的执行功能结束后才会添加一个定时器setTimeout(),而setInterval()则是每隔10毫秒,就尝试回调函数,而不管上一个回调函数是否执行完毕。
所以有下述结论
1. 在js是单线程执行,异步事件必须排队等待执行。
2. 如果无法立即执行定时器,改定时器就会被推迟到下一个进程空闲的时间点上执行。
3. setTimeout()和setInterval()的执行周期是完全不同的。
4.


2. 定时器用法1:处理大量计算过程

js单线程的本质可能是用js实现复杂操作的一个陷阱,在js执行繁忙的时候,浏览器的用户交互,最好的情况是稍有缓慢,最差的情况则是反应迟钝,这随时可能导致浏览器随时挂掉,因为在js脚本执行的时候页面所有渲染都要停止,但是如果我们要同时操作上千个DOM的时候,就会引起页面卡顿,这个时候定时器就来救我们了 ,它可以暂停一段代码,让其在空闲的时间去执行
一下是一个例子

<table><tbody></tbody></table><script type="text/javascript"> var tbody=document.getElementByTagName("tbody")[0];//找到tbody元素   for(var i=0;i<20000;i++){    var tr=document.createElement("tr")//创建大量行元素    for(var t=0;t<6;t++){     var td=document.createElemnt("td")//创建列     td.appendChild(doument.creatTextNode(i+","+t))//每个列都有个文本节点     tr.appendChild(td);//将列元素塞入行    }    tbody.appendChild(tr);//将每一行插入tbody  }</script>

在本例中我们创建了240000个DOM节点,并使用大量单元格来填充一个表格,这是非常大量的DOM操作,明显会增加浏览器的执行时间,从而阻断正常的用户交互操作,定时器的作用就来了,在代码执行的时候可以暂停休息,修改后的代码如下

<!DOCTYPE html><html><head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <meta http-equiv="X-UA-Compatible" content="ie=edge" />    <title>Document</title></head><body>    <table>        <tbody>        </tbody>    </table></body><script type="text/javascript">    var rowCount=20000;//有多少行    var divideInto=4;  //分几步    var chunkSize=rowCount/divideInto; //每步执行多少行    var iteration=0; //当前步    var table=document.getElementsByTagName("tbody")[0];    setTimeout(function generateRows(){        var base=(chunkSize)*iteration;  //计算上次中断地方        for(var i=0;i<chunkSize;i++){            var tr=document.createElement("tr");            for(var t=0;t<6;t++){                var td=document.createElement("td");                td.appendChild(document.createTextNode((i+base)+","+t+","+iteration));                tr.appendChild(td);            }            table.appendChild(tr);        }        iteration++;//步数增加        if(iteration<divideInto){            setTimeout(generateRows,0); //下一阶段        }    },0);</script></html>

3.定时器用法2:中央定时器控制

在使用定时器出现的问题就是对大批定时器的管理,这在处理动画效果时尤为重要,所以在操纵大量定时器属性的时候,我们需要用一种方式来管理他们
同时管理多个定时器会有多问题,如如何保留大量间隔定时器的应用,然后还得取消他们(尽管可以使用闭包这种方法),而且还干扰了浏览器的正常运行(这还要看不同浏览器的垃圾处理机制)。
在一个简单轮播图中 自动播放与运动函数 两个定时器叠加在一起,可能会发现在一个浏览器运行良好,可到了另一个浏览器里 却变的非常的卡顿,甚至是崩掉。所以现在动画都使用一种为中央定时器控制的技术

  • 每个页面在同一时间只需要运行一个定时器
  • 可以根据需要恢复和暂停定时器
  • 删除回调函数过程变得很简单
<script type="text/javascript"> var timer = { //一个定时器控制对象    timerID: 0, //记录状态    timers: [],    add: function(fn) {        this.timers.push(fn);    }, //添加定时器函数    start: function() {        if(this.tiemrID) return;  //若已有定时器运行 返回        (function runNext() {       //若定时器序列中有定时器            if(timer.timers.length>0){       //寻找序列中的定时器 看那个执行完毕            for(var i=0;i<timer.timers.length;i++){               if(timer.timers[i]()===false){                timer.timers.splice(i,1);                i--;   //清除定时器 i--               }            }     //直到序列中定时器被清除完毕,结束            timer.timerId=setTimeout(runNext,0);  //进行下一次调用          }          })();  //闭包保存所有属性值    },            //开启函数    stop:function(){        clearTimeout(this.timerID);        this.timerID=0;   //停止函数    } };</script>

这里的核心就是start方法,首先确认没有定时器在运行,如果没有就立即执行一个即时函数(闭包保存属性)来开启中央处理定时器 ,在这个即时函数内,如果有处理程序,就遍历每一个,如果有某个程序完成返回false我们就从数组中删除。然后进入下一次调用

我们来测试一下

CTYPE html><html>    <head>        <meta charset="utf-8">        <title></title>    </head>    <body>        <div id="box" style="position: relative;">            hello!        </div>    </body>    <script type="text/javascript">        var timer = { //一个定时器控制对象            timerID: 0, //记录状态            timers: [],            add: function(fn) {                this.timers.push(fn);            }, //添加定时器函数            start: function() {                if(this.tiemrID) return; //若已有定时器运行 返回                (function runNext() { //若定时器序列中有定时器                      if(timer.timers.length > 0) { //寻找序列中的定时器 看那个执行完毕                        for(var i = 0; i < timer.timers.length; i++){                            if(timer.timers[i]() === false) {                                timer.timers.splice(i, 1);                                i--; //清除定时器 i--                            }                        } //序列中定时器被清除完毕,结束                        timer.timerID = setTimeout(runNext, 0);                    }                })(); //闭包保存所有属性值            }, //开启函数            stop: function() {                clearTimeout(this.timerID);                this.timerID = 0; //停止函数            }        };        var box = document.getElementById("box"),            x = 0,            y = 20;        timer.add(function() {            box.style.left = x + "px";            if(++x > 500) return false;        });        timer.add(function() {            box.style.top = y + "px";            y += 2;            if(y > 500) return false;        });        timer.start();    </script></html>

这种方式组织定时器,可以确保回调函数总是按照顺序执行,而普通的定时器 并不会这样。
这种方法还有另一种作用

4.定时器用法3:异步测试

当我们要对还没完成操作的代码执行测试的时候,我们需要将这种测试从测试套件中分离出来,以便测试是否异步

(function() {    var queue = [],        paused = falsed; //状态表    this.test = function(fn) {        queue.push(fn);    //定义测试函数        runTest();    };    this.paused = function() {        paused = true;       //定义停止测试函数    }    this.resume = function() {        paused = false;                    setTimeout(runTest,1);   //定义恢复测试函数    }    function runTest(){        if(!paused&&queue.length){            queue.shift()();            if(!paused)             resume();        }                        }                              //运行测试函数})();  //这段代码只是将异步的代码按添加的顺序执行 已测试 即在等待执行的时候,执行队列中的第一个否则就完全停止

总之js中的定时器是很有用的,看似简单的特性但实际实现包含着许多陷阱

在复杂的应用中,定时器就显的格外重要 如计算密集型代码,动画,异步测试