定时器存在的弊端,以及解决的方法
javascript是一大特点是单线程,也就是说同一时间只能做一件事,单线程也就意味着,所有任务需要排队,前一个任务执行完,才会执行后一个任务,如果前面一个任务耗时很长,后一个任务就不得不等。
如果排队是因为CPU忙不过来也就算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如ajax从操作从网络读取数据),不得不等着结果出来再往下进行。
javascript语言的设计者意识到,这时候主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务,等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有的任务可以分成两种,一种是同步任务,另一种是异步任务,同步任务指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才会执行下一个任务,异步指的是不进入"主线程"而是进入"任务队列的"的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
具体来说,异步执行的运行机制如下:
1.所有同步任务都在主线程上执行,形成一个执行栈,
2.主线程之外还存在一个"任务队列",只要异步任务有了结果,就会在"任务队列"中放置一个事件
3.一旦执行栈中所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件,哪些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
4.主线程不断重复上面的第三步骤
主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环);
除了放置异步任务的事件,"任务队列"还可以放置定时事件,setTimeout(fn,0)的含义是,指定任务在主线程最早可得的空闲时间执行,也就是说,尽可能早的执行,他在"任务队列"尾部添加一个事件,因此要等到同步任务和"任务队列"现在的事件执行完以后,才会得到执行
需要注意的是,定时器只是在指定的时间将事件插入到"任务队列",必须等到执行栈中当前的代码(同步任务以及"任务队列"当前任务)执行完以后,主线程才会执行他指定的回调函数,如果当前代码执行耗时很长,有可能要等很久,所以并没有办法保证,回调函数一定在定时器规定的时间执行,
比如下面的代码:
<script>
for(var i=0;i<900000000;i++){
// ...
}
console.log(0);
setTimeout(function(){
console.log(1);
},1000)
</script>
上面的代码,定时器在1000毫秒时加入"任务队列",但是此时同步代码for循环还没有执行完,可能需要很长的时间,所以当for执行完以后会立即输出0和1,0和1的输出也没有体现出间隔1000毫秒的时间,而此时setTime回调函数执行的时候时间远远大于1000毫秒
再比如:
< script > startFn2(); function startFn2() { var p2 = new AlarmClockByInterval(callBackByTest, 2000); } function callBackByTest() { var i = 0; for (; i < 9000000000; i++) { } return true; } function AlarmClockByInterval(_args1, _args2) { var timeFn, self = this; callBackFn = _args1; ms = _args2; i = 0; this.getInterval = function() { if (ms) { if (!timeFn) { timeFn = setInterval(function() { console.log("定时任务开始执行:" + new Date().getTime()); callBackFn(); console.log("定时任务结束执行:" + new Date().getTime()); }, ms) } } else { closeInterval(); } } this.closeInterval = function() { if (timeFn) { closeInterval(timeFn); } } self.getInterval(); } < /script>
上面的代码,定时任务一旦执行完毕,会立即执行下一次任务,并没有理想中的间隔,直接用setInterval并不能获得我们理想中的效果
对于上面的问题,我们如何解决呢?
使用setTimeOut,和递归,利用函数自动调用自身,延时执行就可以很好的解决这个问题
<script> function AlarmClockByTimeOut(_args1,_args2){ var _type = 0, timeFn, _flag = true, ms = _args2, callBackFn = _args1,self = this; this.getTimeOut=function(){ var _callee = arguments.callee; if(_flag){ if(_type==0){ timeFn=setTimeout(function(){ console.log("定时任务开始执行:" + new Date().getTime()); _flag=callBackFn(); console.log("定时任务结束执行:" +new Date().getTime()); _callee(); },ms) }else{ if(timeFn){ clearTimeout(timeFn); }else{ console.error(500, "定时器已终止,外部终止..."); } } }else{ if (timeFn) clearTimeout(timeFn); console.error(500, "定时器已终止,回调函数出现错误或内部强制终止..."); } } this.close = function(_args1) { _type = _args1 || 1; }; self.getTimeOut(); } function callBackByTest() { var i = 0; for (; i < 900000000; i++) { } return true; } function startFn2() { var p1 = new AlarmClockByTimeOut(callBackByTest,2000); } startFn2(); </script>