tti-polyfill实现原理

可交互时间
可交互时间 (TTI) 指标用于标记应用已进行视觉渲染并能可靠响应用户输入的时间点。 应用可能会因为多种原因而无法响应用户输入:

页面组件运行所需的 JavaScript 尚未加载。
耗时较长的任务阻塞主线程(超过50ms)
TTI 指标可识别页面初始 JavaScript 已加载且主线程处于空闲状态(没有耗时较长的任务)的时间点(请结合下面说明体会下这句话)。

如果嫌麻烦直接看最下面总结就好
如果嫌麻烦直接看最下面总结就好
如果嫌麻烦直接看最下面总结就好

1.通过PerformanceObserver 监听资源的加载
判断标准有负责 回头去看看原理

  _registerPerformanceObserver() {
    this._performanceObserver = new PerformanceObserver((entryList) => {
      const entries = entryList.getEntries();
      for (const entry of entries) {
        if (entry.entryType === 'resource') {
        //运行所需资源加载
          this._networkRequestFinishedCallback(entry);
        }
        if (entry.entryType === 'longtask') {
        //长任务
          this._longTaskFinishedCallback(entry);
        }
      }
    });
    this._performanceObserver.observe({entryTypes: ['longtask', 'resource']});
  }

2.监听load
load里执行 this.startSchedulingTimerTasks(),这个函数意义是在5秒内没有超过2个请求判断网络有问题

  getFirstConsistentlyInteractive() {
    return new Promise((resolve, reject) => {
      this._firstConsistentlyInteractiveResolver = resolve;

      if (document.readyState == 'complete') {
        this.startSchedulingTimerTasks();
      } else {
        window.addEventListener('load', () => {
          // You can use this to set a custom minimum value.
          // this.setMinValue(20000);
          // Starts scheduling the timer that checks for network quiescence (a 5-second
         // window of no more than 2 in-flight network requests)
    
          this.startSchedulingTimerTasks();
        });
      }
    });
  }
 // Starts scheduling the timer that checks for network quiescence (a 5-second
         // window of no more than 2 in-flight network requests)
  startSchedulingTimerTasks() {
    log(`Enabling FirstConsistentlyInteractiveDetector`);

    this._scheduleTimerTasks = true;
//获取最后一个长任务, 长任务在chrome判断标准应该是50ms
    const lastLongTaskEnd = this._longTasks.length > 0 ?
        this._longTasks[this._longTasks.length - 1].end : 0;

//在resource加载请求的节点(不包含长任务的)和正在进行xhr请求的(xhr请求的资源有可能是导致主线程是否进行下去的前置条件),找到超过2条正在加载的xhr 或者 并行加载resource中哪个资源加载完还有2条resource还在加载的时间节点。可以看t-1图中的箭头这个资源加载完的结尾后面还有2条资源在加载,注意这里2条资源和箭头所指资源加载没有空隙,(每当新的请求结束都会触发computeLastKnownNetwork2Busy方法再去重置网络监测定时器。)
    const lastKnownNetwork2Busy =
        firstConsistentlyInteractiveCore.computeLastKnownNetwork2Busy(
            this._incompleteRequestStarts, this._networkRequests);

    this.rescheduleTimer(
        Math.max(lastKnownNetwork2Busy + 5000, lastLongTaskEnd));
  }
tti-polyfill实现原理_第1张图片
t1.png

检测网络静止

重置检查网络静止的计时器,一个请求结束或者一个长任务执行完(这里请求和长任务指的是load和load之后是resource和longtask)判断5s内有没有网络请求和长任务。如果有则用新的时间节点重置时间定时器。

  rescheduleTimer(earliestTime) {
    // Check if ready to start looking for firstConsistentlyInteractive
    if (!this._scheduleTimerTasks) {
      log(`startSchedulingTimerTasks must be called before ` +
          `calling rescheduleTimer`);

      return;
    }

    log(`Attempting to reschedule FirstConsistentlyInteractive ` +
        `check to ${earliestTime}`);
    log(`Previous timer activation time: ${this._timerActivationTime}`);

    if (this._timerActivationTime > earliestTime) {
      log(`Current activation time is greater than attempted ` +
          `reschedule time. No need to postpone.`);

      return;
    }
    clearTimeout(this._timerId);
    this._timerId = setTimeout(() => {
      this._checkTTI();
    }, earliestTime - performance.now());
    this._timerActivationTime = earliestTime;

    log(`Rescheduled firstConsistentlyInteractive check at ${earliestTime}`);
  }

5

  _checkTTI() {
//searchStart:理解成domContentLoadedEventEnd节点
//minValue:domContentLoadedEventEnd节点
//lastBusy:当前节点时,存在超过2条并行网络请求的节点,参考第三条
  ...
 const maybeFCI =
        firstConsistentlyInteractiveCore.computeFirstConsistentlyInteractive(
            searchStart, /** @type {number} */ (minValue), lastBusy,
            currentTime, this._longTasks);

    if (maybeFCI) {
      this._firstConsistentlyInteractiveResolver(
          /** @type {number} */ (maybeFCI));
      this.disable();
    }
...
}

最后从长任务尾节点->searchStart->minValue取值放回就是tti时间

// Have not reached network 2-quiet yet.
  if ((currentTime - lastKnownNetwork2Busy) < 5000) return null;

  const maybeFCI = longTasks.length === 0 ?
      searchStart : longTasks[longTasks.length - 1].end;

  // Main thread has not been quiet for long enough.
  if (currentTime - maybeFCI < 5000) return null;
//这个就是返回的tti时间
  return Math.max(maybeFCI, minValue);

总结:整个tti就是在一个5s内(为什么是5s 有知道的同学请告知下)没有新请求或者新的js长任务的状态下, 找最后一个长任务的节点,如果长任务不存在用domContentLoadedEventEnd节点来当做可交互时间点。
domContentLoadedEventEnd 结束当前文档的事件是可绑定的,所以页面的交互是可执行的。
为什么用长任务时间节点呢,因为js是单线程的,主线程在执行长任务,页面上交互自然是阻塞的。
tti只是一个页面性能参考标准。可以拿来做横向指标的比较。

你可能感兴趣的:(tti-polyfill实现原理)