Q5 · 性能指标

如何用代码获取 TTI?指标分数段?怎么提升?

TTI可交互时间长任务

⚡ 速记答案(30 秒)

  • 概念:TTI = 页面已经可见且主线程"足够空闲"并能快速响应输入的时间点
  • 获取:可基于 longtask、FCP、网络静默等信号自己实现,或用 Lighthouse 算法
  • 分数段(参考 Lighthouse):好 ≈ < 3.8s,一般 3.8–7.3s,差 > 7.3s
  • 拆分长任务,避免一次执行大量 JS
  • 代码分割、按需加载、减少同步计算
  • 利用 requestIdleCallback、Web Worker 把非关键计算移出主线程

📖 详细讲解

TTI 定义


Time to Interactive:页面完全可交互的时间点,满足:


1. 页面已显示可见内容(FCP 之后)

2. 页面对大多数可见元素绑定了事件

3. 主线程足够空闲(5 秒内没有长任务)


长任务(Long Task)


超过 50ms 的任务会阻塞主线程,影响用户交互。


优化策略


1. 拆分长任务

• 使用 requestIdleCallback 分批处理

• 使用 setTimeout(fn, 0) 让出主线程


2. 减少 JS 执行时间

• 代码分割

• 延迟非关键脚本

• Tree-shaking


3. 移出主线程

• Web Worker 处理计算密集型任务

💻 代码示例

监听长任务
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('长任务:', entry.duration, 'ms');
    // duration - 50 = 阻塞时间
    console.log('阻塞时间:', entry.duration - 50, 'ms');
  }
});

observer.observe({ type: 'longtask', buffered: true });
拆分长任务
// 使用 scheduler.yield() (实验性)
async function processLargeArray(items: any[]) {
  for (let i = 0; i < items.length; i++) {
    processItem(items[i]);
    
    // 每处理 100 个项目让出主线程
    if (i % 100 === 0) {
      await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0));
    }
  }
}

// 使用 requestIdleCallback
function processInIdle(tasks: (() => void)[]) {
  requestIdleCallback((deadline) => {
    while (deadline.timeRemaining() > 0 && tasks.length > 0) {
      const task = tasks.shift();
      task?.();
    }
    if (tasks.length > 0) {
      processInIdle(tasks);
    }
  });
}
💡
面试技巧:回答性能问题时,先说指标和标准,再讲优化手段,最后结合实际项目经验。