面试必看的性能指标

356 阅读5分钟

性能指标概述

说到网页性能,我们就像是在调教一辆赛车——你得让它跑得快,还要让乘客坐得舒服。为此,我们得盯紧几个关键指标:首次有意义渲染时间(FMP)、首次内容绘制时间(FCP)、可交互时间(TTI)、页面加载时间(PLT),以及最大内容绘制时间(LCP)。FCP 和 PLT 可以通过浏览器自带的工具轻松拿到,而 FMP 和 TTI 则需要我们施展点“黑魔法”,用算法逻辑来推测。

整个代码的风格是以ts来写的,使用rollup来打包的,大小在10kb 左右。

项目仓库链接:PerformanceMonitor

image.png

一、首次有意义渲染时间(First Meaningful Paint, FMP)

  1. 定义: FMP 就是用户眼前一亮的那一刻——当他们终于看到页面的主要内容(如文本、图片等)时,这个时间点就是 FMP。它是用户真正开始觉得“哇,我终于看到了有用的东西!”的时刻。
  2. 实现方法:
    • 用 MutationObserver 监听 DOM 变化,记录页面各部分的渲染时间。
    • 计算当前 DOM 树的得分,找到分数变化最大的一刻,那就是 FMP 的时间点。
  3. 衡量标准:
    • 理想的 FMP 时间在 1-2 秒以内——这样用户才不会失去耐心。
    • FMP 时间过长会让用户觉得页面“有点慢”,所以最好优化页面结构和资源加载。
private calculateDOMScore(): number {
    let score = 0;
    const stack: { node: Element, depth: number }[] = [{node: document.body, depth: 0}];

    while (stack.length > 0) {
        const {node, depth} = stack.pop()!;
        if (!node) {
            score += 5;
            continue
        }
        ;

        const weight = this.getNodeWeight(node);
        const {width, height, areaPercent} = this.getNodeMetrics(node);

        // 根据公式计算节点的分数
        const nodeScore = (width || 1) * (height || 1) * (weight || 1) * areaPercent;
        score += nodeScore;

        // 将子节点添加到栈中
        const children = node.children;
        for (let i = 0; i < children.length; i++) {
            stack.push({node: children[i], depth: depth + 3});
        }
    }

    return score;
}

二、首次内容绘制时间(First Contentful Paint, FCP)

  1. 定义: FCP 是页面加载时,用户最早看到的内容元素(比如文本、图片、SVG 等)绘制到屏幕上的时间。换句话说,这是用户感知“页面开始加载”的那一刻。
  2. 实现方法:
    • 使用 Performance API 中的 performance.getEntriesByType('paint') 获取 FCP 时间点。
    • 浏览器自动记录 first-contentful-paint 事件,直接从性能条目中提取即可。
  3. 衡量标准:
    • 理想的 FCP 时间应在 1.8 秒以内。
    • 超过这个时间,用户可能会觉得“咦,这页面是不是有点慢?”

三、可交互时间(Time to Interactive, TTI)

  1. 定义: TTI 是页面从渲染完毕到用户可以开始点击、滑动并获得响应的时间点。通俗点说,这时候页面就不再“摆设”,可以“玩耍”了。
  2. 实现方法:
    • 从起始点(FCP 或 FMP)开始,找到一个不小于 5 秒的“静默窗口期”(没有长任务且网络请求数不超过 2 个)。
    • 找到静默窗口期后,再找到最近的长任务,长任务结束的时间就是 TTI。
    • 如果没有找到长任务,就用起始点时间作为 TTI。
    • 如果前两步得到的 TTI 比 DOMContentLoadedEventEnd 还早,就用 DOMContentLoadedEventEnd 作为 TTI。
  3. 衡量标准:
    • 理想的 TTI 时间应在 5 秒以内——用户等待时间太长可不是个好体验。

四、最大内容绘制时间(Largest Contentful Paint, LCP)

  1. 定义: LCP 是指页面加载过程中,最大内容元素(如大图或主块级文本)绘制到屏幕上的时间点。说白了,就是用户看到页面上“最显眼”的内容的那一刻。
  2. 实现方法:
    • LCP 的计算方式非常直观。浏览器在加载页面时,会追踪所有可见元素(如图片、文本、视频等)的加载情况,并记录最大的那个元素的加载时间。
    • 浏览器会考虑:图像元素的加载、文本元素的加载、iframe 内容的加载以及背景图像的加载。
  3. 衡量标准:
    • 理想的 LCP 时间应在 2.5 秒以内。
    • 超过 4 秒就显得有点“拖沓”,需要优化页面资源和结构。
private calculateTTI(): void {
    this.observer?.disconnect();

    // 使用 FMP 作为起始点,如果没有 FMP,则使用 FCP
    const startTime = this.fmpTime || this.fcpTime;

    if (!startTime) {
        console.warn('没有FMP或者FCP,无法计算TTI');
        return;
    }

    const quietWindow = this.findQuietWindow(startTime, QuietWindowDuration);
    if (!quietWindow) {
        console.log('没有静默窗口期');
        this.ttiTime = startTime;
    } else {
        const lastLongTask = this.findLastLongTaskBefore(quietWindow.end);
        console.log('静默窗口期:', lastLongTask)
        // @ts-ignore
        this.ttiTime = lastLongTask ? lastLongTask?.duration + lastLongTask?.startTime  : (startTime);
    }
    //


    /**
     * timing.navigationStart :浏览器处理当前网页的启动时间
     * domContentLoadedEventEnd:返回当前网页所有需要执行的脚本执行完成时的Unix毫秒时间戳
     */
    const timing = window.performance.timing;
    const domContentLoadedEventEnd = timing.domContentLoadedEventEnd - timing.navigationStart;

    if (Number(this.ttiTime) < domContentLoadedEventEnd) {
        this.ttiTime = domContentLoadedEventEnd;
    }

    if (!this.ttiTime) return console.warn('没有TTI');
    this.callback({
        id: new Date().getTime().toString(),
        name: 'TTI',
        value: this.ttiTime,
        rating: getRating(this.ttiTime, TTIThresholds),
    })
}

五、页面加载时间(Page Load Time, PLT)

  1. 定义: PLT 是从页面开始加载到完全加载完毕的时间。它包括所有资源的下载和渲染,反映整个页面加载的时间。就像赛车从起点到终点的总时间一样。
  2. 实现方法: 使用 window.performance.timing 获取从 navigationStartloadEventEnd 的时间差来计算 PLT。
  3. 衡量标准: 理想的 PLT 时间应在 2-3 秒以内——超过这个时间,用户可能会觉得“这车怎么开得这么慢”。

六、指标之间的关系

  1. FCP(首次内容绘制):

    • FCP 是页面加载中第一个重要的时间点,通常是所有指标中最早发生的。
    • FCP 通常小于 FMP、LCP 和 TTI。
  2. FMP(首次有意义的绘制):

    • FMP 通常出现在 FCP 之后,标志着用户感知到的主要内容首次被绘制。
    • 在复杂页面中,FMP 通常介于 FCP 和 LCP 之间。
  3. LCP(最大内容绘制):

    • LCP 通常在 FMP 之后,标志着页面的主要内容已经加载完成。
    • LCP 通常大于 FMP,接近 TTI。
  4. TTI(可交互时间):

    • TTI 是所有指标中最晚的,标志着页面已经完全准备好供用户交互。
    • TTI 通常是最大的,超过 FCP、FMP 和 LCP。

七、简单页面上的指标关系

在简单页面中,这些指标通常遵循更为线性和一致的顺序:

  • FCP ≤ FMP ≤ LCP ≤ TTI

而且:

  • FCP 通常是最小的,因为它标志着第一个内容的渲染。
  • FMP 略大于 FCP,标志着有意义内容的首次绘制。
  • LCP 略大于 FMP,标志着最大内容的绘制完成。
  • TTI 通常是最大的,表明页面已经完全可交互。

希望这些性能指标能帮助你像调教赛车一样,让你的页面跑得又快又稳。