温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

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

发布时间:2020-06-05 14:31:24 来源:网络 阅读:733 作者:yerikyu 栏目:web开发

最近笔者在开发的过程中,因为需要对页面进行动态渲染,又不想一直刷新页面数据,而是让浏览器根据客户需要动态对渲染数据,这个时候就用到了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/

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI