最近笔者在开发的过程当中,由于须要对页面进行动态渲染,又不想一直刷新页面数据,而是让浏览器根据客户须要动态对渲染数据,这个时候就用到了setInterval这个函数,但是这个函数和setTimeout()相比又有什么区别呢?咱们知道:javascript
setInterval()执行方法实际上是将需执行的代码加入到任务队列,直到轮到代码执行时,肯定时间是否已经到了,若到达则执行代码java
var startTime=new Date(); var func = function(){ console.log('start: ' + (new Date()-startTime)); for(var i=0; i<1000000000; i++){} console.log('end: ' + (new Date()-startTime)); }; setInterval(func,1000);
上面这段代码的执行以后,你会发现setInterval的end与start时间跳动很是大,并非咱们设置的1000ms。因为setInterval是一开始就标定了执行的时间点,当所注册的函数(func)超过,所以不会是固定的1000ms。
setTimeout()的用法大体与setInterval相同,不一样的在于定时执行,咱们一样来测试延迟执行的现象浏览器
var startTime=new Date(); var func = function(){ console.log('start: ' + (new Date()-startTime)); for(var i=0; i<1000000000; i++){}; console.log('end: ' + (new Date()-startTime)); setTimeout(func,1000); }; setTimeout(func,1000);
观察下图能够发现 setTimeout 所设置的时间间隔,会由于目前任务队列所执行的代码而可能发生延误执行的状况,咱们能够发现上面这段代码,执行func的end与start时间间隔基本上是符合咱们所设定的1000ms
透过现象看本质,咱们不妨看下这两个函数的源码
从函数声明能够知道setInterval这里容许传参,容许咱们传入一个方法,让其在必定时间(number)后执行,其时间等待依赖于调度器_scheduler
,而方法的执行是由_fnAndFlush
来负责调控的,不过因为有_requeuePeriodicTimer
这个方法的存在使得时间间隔不是咱们所设固定1000msapp
.... case 'setInterval': task.data!['handleId'] = this._setInterval( task.invoke, task.data!['delay']!, Array.prototype.slice.call((task.data as any)['args'], 2)); break; .... private _setInterval(fn: Function, interval: number, args: any[]): number { let id = Scheduler.nextId; let completers = {onSuccess: null as any, onError: this._dequeuePeriodicTimer(id)}; let cb = this._fnAndFlush(fn, completers); // Use the callback created above to requeue on success. completers.onSuccess = this._requeuePeriodicTimer(cb, interval, args, id); // Queue the callback and dequeue the periodic timer only on error. this._scheduler.scheduleFunction(cb, interval, args, true); this.pendingPeriodicTimers.push(id); return id; }
先来看下_fnAndFlush
的代码,这个函数实际上是将咱们的所须要执行的函数刷入内存中,等待浏览器的执行ide
private _fnAndFlush(fn: Function, completers: {onSuccess?: Function, onError?: Function}): Function { return (...args: any[]): boolean => { fn.apply(global, args); if (this._lastError === null) { // Success if (completers.onSuccess != null) { completers.onSuccess.apply(global); } // Flush microtasks only on success. this.flushMicrotasks(); } else { // Failure if (completers.onError != null) { completers.onError.apply(global); } } // Return true if there were no errors, false otherwise. return this._lastError === null; }; }
咱们不妨来看下_requeuePeriodicTimer
这个方法所完成的功能的特色。其会将浏览器内存中存在的函数加入队列中执行(若是存在的话),函数功能完成过程当中已经进入下个scheduler的执行周期,即函数在执行过程当中还有一个计时器在等待下个函数的执行。正如实验中,咱们观察到的全部的start间隔大体是1000ms函数
private _requeuePeriodicTimer(fn: Function, interval: number, args: any[], id: number): Function { return () => { // Requeue the timer callback if it's not been canceled. if (this.pendingPeriodicTimers.indexOf(id) !== -1) { this._scheduler.scheduleFunction(fn, interval, args, true, false, id); } }; }
对于setTimeout这里容许传参,容许咱们传入一个方法,让其在必定时间(number)后执行,其时间等待依赖于调度器_scheduler,而调度器正是让咱们的函数时间间隔符合咱们设置的1000ms的缘由,而方法的执行则是由_fnAndFlush来负责调控的,由于setTimeout并不会将执行函数推入队列中,所以计时器不会运行直到前一个周期的函数都执行完毕以后才开始进入下一个周期,正如实验代码中所写的等待1000ms源码分析
... case 'setTimeout': task.data!['handleId'] = this._setTimeout( task.invoke, task.data!['delay']!, Array.prototype.slice.call((task.data as any)['args'], 2)); break; ... private _setTimeout(fn: Function, delay: number, args: any[], isTimer = true): number { let removeTimerFn = this._dequeueTimer(Scheduler.nextId); // Queue the callback and dequeue the timer on success and error. let cb = this._fnAndFlush(fn, {onSuccess: removeTimerFn, onError: removeTimerFn}); let id = this._scheduler.scheduleFunction(cb, delay, args, false, !isTimer); if (isTimer) { this.pendingTimers.push(id); } return id; }
参考资料:
https://www.jeffjade.com/2016/01/10/2016-01-10-javacript-setTimeout/
https://www.jeffjade.com/2016/01/10/2016-01-10-javaScript-setInterval/测试