定时器存在的弊端,以及解决的方法

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>

  

posted @ 2017-07-18 15:35  吕瑞芳  阅读(1911)  评论(0编辑  收藏  举报