前端最全性能指标与排查方法

1,867 阅读5分钟

一、前端性能指标

1.1、LCP(Largest Contentful Paint 最大内容渲染时间)

可视窗口内最大元素绘制时间,在用户第一次交互后停止记录,并且随着页面渲染而变化,因为页面中最大元素随着渲染会变化。

LCP评价
<=2500ms良好
<=4000ms需要改进
>4000ms响应缓慢

1.2、INP (Interaction to Next Paint)

衡量与网页进行的每个点按、点击或键盘互动的延迟时间,并根据互动次数选择网页最差(或接近最长的互动延迟时间)作为单个代表性值,以描述网页的整体响应能力。

INP评价
<200ms良好
<500ms需要改进
>=500ms响应缓慢

1.3、CLS(Cumulative Layout Shift 累计布局偏移)

用于衡量视觉稳定性,网页在生命周期内发生的每一次布局意外偏移的布局偏移得分的最高累计分数。

得分=影响百分比 * 距离百分比

影响百分比=该帧和上一帧中所有不稳定元素的可见区域的组合 / 视口总面积=红色区域高度 / 手机屏幕高度

距离百分比=该帧和上一帧中所有不稳定元素的移动距离 / 视口总面积 = 红色区域中空白部分高度 / 手机屏幕高度

CLS评价
<0.1良好
<=0.25需要改进
>2.5响应缓慢

2、其他重要指标

2.1、FCP( First Contentful Paint 首次内容渲染时间)

页面第一次绘制图片,文本,非空白canvas和svg的时间。

FCP评价
<=1800ms良好
<=3000ms需要改进
>1800ms响应缓慢

2.2、TTFB(Time to First Byte 加载第一个字节所需时间)

衡量请求资源到响应第一个字节之间的时间。

TTFB=重定向时间 + Service Worker启动时间 + DNS查找 + 连接和TLS协议 + 请求,直到收到响应的第一个字节为止。

TTFB评价
<800ms良好
<1800ms需要改进
>=1800ms响应缓慢

3、其他指标

3.1、FP(First Paint 首次绘制时间)

页面第一次绘制像素的时间

3.2、TBT(Total Blocking Time 总阻塞时间)

衡量在FCP之后主线程被阻塞的时间足以阻止输入响应的总时间,也就是长任务-50ms的时间和。

存在长任务(主线程上运行超过50ms的任务),主线程就被认为是阻塞状态,这时候用户很大概率会注意到延迟,认为网页运行缓慢或已坏。

二、性能排查方法

1 性能查看方法

  1.1、谷歌插件——Web Vitals

1.2、PageSpeed Insights(chrome工具)

对于需要登陆才能访问的网址不友好,无法查看性能指标(图)(如shoplazza b端retasmart.shoplazza.com/smart_apps/…

暂时无法在飞书文档外展示此内容

2、 开发者工具 性能面板介绍与排查

图2-1 是performance面板介绍,展示了performance主要模块的作用。

图2-1 performance板块简介

在屏幕截图板块,上方部分表示帧率(每秒钟显示的图像帧数量),如果出现了下图框住的红色块,表示有卡顿,应该优化。选择该时间段,从主线程任务板块可以看到阻塞的红色条纹方块,如图2-3所示。在主线程任务板块,可以看出耗时长的原因在于重新计算样式和布局,点击耗时长的任务,在面板最下方,点击自下而上或者调用树,可以看到具体的耗时文件,以及源代码所在文件。

图2-2 出现屏幕卡顿的屏幕截图板块

图2-3 卡顿时间段主线程任务板块

图2-4 任务具体耗时时间和源代码

三、性能优化方法

3.1 优化加载速度

  • 减少dom操作

    •   dom操作会触发浏览器的样式计算,重新布局等操作,消耗性能,也就是减少重排重绘。
    •     //隐藏dom,尽量用visibility,少用display:none
         .div{
             visibility:hidden;
         }
         
         //用文档片段进行批量操作
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < 100; i++) {
          const div = document.createElement('div');
          div.textContent = i;
          fragment.appendChild(div);
        }
        document.getElementById('container').appendChild(fragment);
      
  • 图片懒加载(不一次性加载所有图片,图片到了可视区再加载)

    •   //1、最简单的方法
        <image src="" loading="lazy">
      
        //2、Intersection Observer API 
        //获取所有img节点
        let img = document.getElementsByTagName("img");
        //监听方法
        let observer=new IntersectionObserver((entries, observer)=>{
           entries.forEach(entry=>{
               if(entry.isIntersecting){
                   //将img的data-src的值赋值给src
                   entry.target.setAttribute("src", entry.target.getAttribute("data-src"))
                   //停止监听
                   observer.unobserve(entry.target)
               }
           })
        })
        //调用函数
        Array.from(img).forEach((v) => {observer.observe(v)})
      
        //3、getBoundingClientRect() 
        const ele = img.getBoundingClientRect()
        if(ele.top>0 && ele.top< window.innerHeight){
            img.setAttribute('src',img.getAttribute('data-src'))
        }
      
  • 事件委托(利用事件冒泡机制,将事件处理程序绑定到父元素上,而不是为每个子元素都单独绑定事件)

<ul id="myList">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
 </ul>
const myList = document.getElementById('myList');
myList.addEventListener('click', function (event){
    if (event.target.tagName === 'LI') {
        console.log('Clicked item:', event.target.textContent);
     }
});
  • 浏览器缓存

    • 强制缓存

    •   在请求头设置Cache-Control: max-age; 设置Expires

    •   // 对于/static路径下的资源,设置缓存时间为3600秒(1小时)。
        location /static {
            // 服务器根目录
            root   /var/www/html;
            add_header Cache-Control "public, max-age = 3600";
         }
         
         location ~* .(css|js|png|jpg|jpeg|gif)$ {
             root   /var/www/html;
             expires 3d;
         }
      
    • 协商缓存

    •   比较 Last-Modified / If-Modified-Since

    •   location ~* .(css|js|png|jpg|jpeg|gif)$ {
            root   /var/www/html;
            expires 3d;
            add_header Last-Modified $date_gmt;
            //根据请求头中的If-Modified-Since字段来判断资源是否有更新,如果有更新则返回新的资源,否则返回304状态码
            if_modified_since exact $http_if_modified_since;
            }
      
    •   比较 ETag/If_None_Match

    •   location ~* .(css|js|png|jpg|jpeg|gif)$ {
            root   /var/www/html;
            expires 3d;
            //开启etag
            etag on;
            //根据请求头中的If-None-Match字段来判断资源是否有更新,如果有更新则返回新的资源,否则返回304状态码 
            if_none_match $http_if_none_match;
        }
      

3.2 优化互动性

  • 减少长任务

    •   //方法1 手动拆分
        function allFunc () {  
             func1();  
             func2();  
             func3();  
             
             setTimeout(() => {    
                 func4();    
                 func5();  }, 0);
            }
      
        //方法2 把要调用的函数放在栈里 用async await调用
        async function allFunc () {  
         const tasks = [           func1,           func2,           func3,           func4,           func5]  
         while (tasks.length > 0) {    
            const task = tasks.shift();    
             task();    
             await yieldToMain();  
        }}
      
  • 避免复杂的样式计算
//错误代码
function computeWidth () {  
for (let i = 0; i < paragraphs.length; i++) {    
    paragraphs[i].style.width = `${box.offsetWidth}px`; 
    document.getElementsByClassName("c");
}}
//正确代码
const width=box.offsetWidth
function computeWidth () {  
for (let i = 0; i < paragraphs.length; i++) {    
    paragraphs[i].style.width = `${width}px`;   
}}

3.3 优化视觉稳定性

  • 图片设置宽高或者比例
<img src="img1.jpg" width="640" height="360" alt="img1">
  • 广告嵌入资源最后加载,并在可视窗口底部插入,并预留min-height