资讯

精准传达 • 有效沟通

从品牌网站建设到网络营销策划,从策略到执行的一站式服务

setTimeout()与setInterval()的源码分析

最近笔者在开发的过程中,因为需要对页面进行动态渲染,又不想一直刷新页面数据,而是让浏览器根据客户需要动态对渲染数据,这个时候就用到了setInterval这个函数,可是这个函数和setTimeout()相比又有什么区别呢?我们知道:

网站设计制作、成都网站建设的开发,更需要了解用户,从用户角度来建设网站,获得较好的用户体验。创新互联建站多年互联网经验,见的多,沟通容易、能帮助客户提出的运营建议。作为成都一家网络公司,打造的就是网站建设产品直销的概念。选择创新互联建站,不只是建站,我们把建站作为产品,不断的更新、完善,让每位来访用户感受到浩方产品的价值服务。

  • setTimeout()是绑定在浏览器window的一个方法,可以通过这个方法指定一段代码或者函数在多少毫秒(ms)后执行,并返回该定时器的编号,当然了我们可以通过clearTimeout()取消代码的执行
  • setInterval()绑定在浏览器window的一个方法,可以通过setInterval指定一段代码或函数定时在多少毫秒(ms)后执行,并传回该定时器的编号,当然了我们可以通过clearInterval()取消代码的执行
    最有效且直接的就是做个小实验感受下:

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));
};
setInterval(func,1000);

上面这段代码的执行之后,你会发现setInterval的end与start时间跳动非常大,并不是我们设置的1000ms。由于setInterval是一开始就标定了执行的时间点,当所注册的函数(func)超过,因此不会是固定的1000ms。
setTimeout() 与 setInterval() 的源码分析
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
setTimeout() 与 setInterval() 的源码分析
透过现象看本质,我们不妨看下这两个函数的源码
setTimeout() 与 setInterval() 的源码分析
从函数声明可以知道setInterval这里允许传参,允许我们传入一个方法,让其在一定时间(number)后执行,其时间等待依赖于调度器_scheduler,而方法的执行是由_fnAndFlush来负责调控的,不过由于有_requeuePeriodicTimer这个方法的存在使得时间间隔不是我们所设固定1000ms

....
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的代码,这个函数其实是将我们的所需要执行的函数刷入内存中,等待浏览器的执行

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/


新闻名称:setTimeout()与setInterval()的源码分析
标题路径:http://cdkjz.cn/article/jhpocp.html
多年建站经验

多一份参考,总有益处

联系快上网,免费获得专属《策划方案》及报价

咨询相关问题或预约面谈,可以通过以下方式与我们联系

大客户专线   成都:13518219792   座机:028-86922220