javaScript中定时器的用法和原理

js中的线程和定时器


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

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

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

js中操作定时器的方法 :

方法格式描述
setTImeoutid=setTimemout(fn,time)启动一个定时器,在一段时间(time)之后执行传入的回调函数 fn,返回一个定时器id用于clear
clearTimeoutclearTimeout(id)如果定时器还没触发,传入id就可以取消该定时器
setintervalid=setinterval(fn,time)启动一个定时器,在每隔一段时间(time)之后执行传入的回调 函数fn,并且返回一个定时器id涌入clear
clearintervalclearinterval(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中的定时器是很有用的,看似简单的特性但实际实现包含着许多陷阱

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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值