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));
  }
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只是一个页面性能参考标准。可以拿来做横向指标的比较。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,828评论 1 32
  • 所有知识点已整理成app app下载地址 J2EE 部分: 1.Switch能否用string做参数? 在 Jav...
    侯蛋蛋_阅读 2,734评论 1 4
  • 不足的地方请大家多多指正,如有其它没有想到的常问面试题请大家多多评论,一起成长,感谢!~ String可以被继承吗...
    启示录是真的阅读 3,121评论 3 3
  • 1,NSObject中description属性的意义,它可以重写吗?答案:每当 NSLog(@"")函数中出现 ...
    eightzg阅读 4,384评论 2 19
  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 4,444评论 0 5

友情链接更多精彩内容