一、前言
在前端,我们有很多功能需要用到定时器。譬如轮询,譬如定时关闭弹框,譬如实现秒表,譬如一段时间后跳转页面等等。因此,我们需要掌握定时器的用法。
二、设置定时器
目前window对象提供有两个方法来实现定时器的效果,分别是window.setTimeout()和window.setInterval()。
其中setInterval()的作用是:使一段代码每过指定时间就运行一次;常用于轮询。
setInterval(function(){ console.log("这是一个setInterval定时器!"); }, 1000);//1000ms=1s,设定的代码循环运行时的间隔时间,单位为ms
而setTimeout()的作用是:使一段代码在指定时间后运行;常用于一次性定时器及轮询。注意,setTimeout()是只运行一次代码的,若要用setTimeout()进行轮询,需要在setTimeout()的代码里再调起当前setTimeout()所属函数,才能达到循环的效果。
setTimeout(function(){ console.log("这是一个setTimeout定时器!"); }, 1000);//1000ms=1s,设定的代码等待运行的时间,单位为ms
三、定时器的缺陷
1.定时器最大的优点,同时也是它最容易出错的一点:定时器所有任务都是由同一个线程来调度。注意:定时器是异步执行的,它会放到所有同步任务都执行完之后再开始执行。
正是因此,定时器简单易用。同时这也导致了定时器所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务,即定时器的重叠。
2.定时器不会被自动销毁,即它所占内存无法被自动回收。如果不手动清除定时器,它会一直占用内存资源。更可怕的是,一旦使用定时器进行轮询,定时器所占的内存资源将会不断上升,若与定时器重叠问题一起出现,常导致页面卡顿。
所以,我们必须学会清除定时器。
四、清除定时器
定时器在调用时,都会返回一个整型的数字,该数字代表定时器的序号,即第多少个定时器,我们可以利用定时器的序号对定时器进行清除。
因此在清除定时器时,我们常在设置定时器时,定义一个变量来记录定时器返回的定时器序号,然后在定时器完成后,调用该序号清除对应定时器。
目前有两个方法来清除定时器,分别对应两种定时器的方法。
其一是clearInterval(obj),对应setInterval()定时器;
var intervalBox = setInterval(function(){ console.log(intervalBox);//打印interval定时器,查看interval定时器效果 }, 1000); clearInterval(intervalBox);//清除interval定时器
其二是clearTimeout(obj),对应setTimeout()定时器;
var timeoutBox = setTimeout(function(){ console.log(timeoutBox);//打印interval定时器,查看interval定时器效果 }, 1000); clearTimeout(timeoutBox);//清除timeout定时器
注:事实上,我们在使用定时器时,常遇到这样的情况:页面跳转时,上一个页面的定时器未被清除,重新回到该页面时,触发定时器,造成定时器重叠。
要解决这个问题,需要分情况。若你的项目使用了vue或react等框架,页面存在钩子函数的话,在销毁页面的钩子里清除定时器即可。若是单页面,没有钩子函数,可以在定时器中去判断该页面的dom元素是否存在来确定是清除定时器还是继续跑定时器:
<div id="pageId"></div> <script type="text/javascript"> var timeBox; function test() { clearTimeout(timeBox); if(document.getElementById('pageId') === null){ return; } timeBox = setTimeout(()=>{ console.log(timeBox); test(); },1000); }; test(); </script>
五、常用setTimeout()代替setInterval()定时器的原因
setTimeout()只执行一次,而setInterval()会循环调用。从理论上来说,我们应该使用setInterval()来执行轮询,但实际上,我们会用setTimeout()调用自身实现循环来代替setInterval(),原因很简单,setInterval()存在两个重大缺陷:
一、setInterval()无视代码错误
与setTimeout()不同,setInterval()中调用的代码报错并不会阻塞setInterval()的循环。如果setInterval()执行的代码由于某种原因出了错,它依然会继续循环调用该代码。
二、setInterval()无视内部代码延迟
不管setInterval()内部的代码需要多久才能完成,哪怕这段代码没有运行完成,setInterval()都会按照设置的时间不断循环该段代码。这就导致setInterval()内部的代码执行不准确或者执行出错。
例如,若使用setInterval()执行ajax轮询,遇到服务器过载、网络差等原因,ajax请求时间过长,到了setInterval()设置的时间,请求依旧未完成,setInterval()依然会发出一个新的ajax请求,最后,你的客户端网络队列会塞满ajax请求。
因此,我们常用setTimeout()调用自身实现循环来代替setInterval()。
六、总结示例
为实现相应功能,定时器不可或缺性,但是若不规范使用定时器,将会造成巨大困扰,轻则内存资源被严重占用,页面卡顿,重则逻辑断裂,出现重大bug。因此我们需要对定时器规范使用,及时清除。
我对定时器的使用有八字总结:随意使用,即时清除。
简单来说,每设置一个定时器,都要对应清除,示例代码如下:
<html> <head> <meta charset="utf-8"> <title>定时器</title> </head> <body> <button onclick="startInterval()">开始Interval</button> <button onclick="stopInterval()">停止Interval</button> <button onclick="startTimeout()">正常Timeout定时器</button> <button onclick="startTimeoutTwo()">循环的Timeout定时器</button> <button onclick="stopTimeout()">停止Timeout</button> <script> var intervalBox;//interval定时器存储器 //设置interval定时器 function startInterval() { clearInterval(intervalBox);//初始化interval定时器,防止定时器重叠 intervalBox = setInterval(function(){//设置interval定时器 console.log(intervalBox);//打印interval定时器,查看interval定时器效果 }, 1000);//定时器间隔时间1000ms } //结束interval定时器 function stopInterval() { clearInterval(intervalBox);//清除interval定时器 } var timeoutBox;//timeout定时器存储器 //设置正常的timeout定时器 function startTimeout() { clearTimeout(timeoutBox);//初始化timeout定时器,防止定时器重叠 timeoutBox = setTimeout(function(){//设置timeout定时器 console.log(timeoutBox);//打印timeout定时器,查看timeout定时器效果 clearTimeout(timeoutBox);//清除当前timeout定时器,timeout定时器只运行一次代码,直接清掉它 // location.href="timer.html";//一段时间后跳转页面是setTimeout的常用场景之一 }, 1000); } //设置循环的timeout定时器 function startTimeoutTwo() { clearTimeout(timeoutBox);//初始化timeout定时器,防止定时器重叠 timeoutBox = setTimeout(function(){ console.log(timeoutBox);//打印timeout定时器,查看timeout定时器效果 startTimeoutTwo();//循环调用函数自身,以达到循环的效果 }, 1000); } // 结束循环的timeout定时器 function stopTimeout() { clearTimeout(timeoutBox); } </script> </body> </html>