window.performance

memory

  1. jsHeapSizeLimit (表示当前页面最多可以获得的 JavaScript 堆大小)
  2. totalJSHeapSize (表示当前页面已经分配的 JavaScript 堆大小)
  3. usedJSHeapSize (表示当前页面 JavaScript 已经使用的堆大小)
  1. redirectCount,重定向的数量,经过多少次重定向进入这个页面
  2. type,进入页面的方式
    • 0,正常进入非刷新,非重定向
    • 1,通过刷新的方式进入
    • 2,通过前进回退按钮进入
    • 255,非是上述情况

PerformanceNavigationTiming

  • PerformanceNavigationTiming
  • 顺序
    1. 页面卸载部分
      • 卸载:navigationStart、unloadEventStart、unloadEventEnd
    2. 网络部分
      • 跳转:redirectStart、redirectEnd
      • 请求文档:fetchStart
      • DNS查询:domainLookupStart、domainLookupEnd
      • 连接建立:connectStart、connectEnd
      • 请求:requestStart、responseStart、responseEnd
    3. 页面解析部分
      • 解析dom:domLoading、domInteractive
      • 资源加载,dom渲染:domContentLoadedEventStart、domContentLoadedEventEnd、domComplete

参考


指标

首次内容绘制(First Contentful Paint)

  • 定义
    • 浏览器从响应用户输入网络地址,到浏览器呈现第一段 DOM 内容所需的时间
    • 首屏时间 = 首屏内容渲染结束时间点 - 开始请求的时间点
    • performance.getEntries(‘paint’).filter(entry => entry.name == ‘first-contentful-paint’)[0].startTime;
  • 优化
    1. 缩短距离: cdn
    2. 减少首次加载需要请求的内容
      • 动态加载: 使用 import() 或 React.lazy() 在路由或组件级别进行模块拆分
      • 懒加载: intersectionObserver

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

  • 定义
    • 指代页面中最大的内容完成绘制的时间。

TTFB

  • 定义
    • 指代从资源的请求到响应第一个字节的时间跨度。
    • responseStart - navigationStart
  • 影响因素
    • 重定向时延,DNS查询时延,链接建立延迟,请求响应时延

可交互时间(Time to Interactive)

  • 定义
    • 页面完全交互所需的时间
  • 优化
    • 把长任务拆分为较小的任务(使用 requestAnimationFrame 或 requestIdleCallback),或者选择在主线程之外(如Web Workers)运行 JavaScript

DCL (DOMContentLoaded)

  • 定义
    • 当HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,无需等待样式,图像和子框架的完成加载的时间。
  • 计算
document.addeventListener('DOMContentLoaded', function() {
    +new Date() - window.performanceObser.timing.navigationStart
}, false);

(Total Blocking Time)

(Cumulative Layout Shift)

白屏时间

  • 从用户开始请求页面时开始计算到开始显示内容结束,中间过程包括DNS查询、建立TCP链接、发送首个HTTP请求、返回HTML文档、HTML文档head解析完毕。
  • 计算:
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>白屏时间计算-常规方法</title>
      <script>
        window.pageStartTime = Date.now()
      </script>
      <link rel="stylesheet" href="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-assets/ionicons/2.0.1/css/ionicons.min.css~tplv-t2oaga2asx-image.image">
      <link rel="stylesheet" href="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-assets/asset/fw-icon/1.0.9/iconfont.css~tplv-t2oaga2asx-image.image">
      <script>
        window.firstPaint = Date.now()
        console.log(`白屏时间:${window.firstPaint - window.pageStartTime}`)
      </script>
    </head>
    <body>
      <div>这是常规计算白屏时间的示例页面</div>
    </body>
    </html>
    
  • 白屏时间 = window.firstPaint - window.pageStartTime

参考


首屏优化


滚屏性能优化

  • touchstarttouchmove事件的passive选项设置为ture
    • 原理: touchstarttouchmovecancelable的。事件处理程序中可能会调用preventDefault(),所以需要等待事件处理程序结束再滚动。
    • 注: 大部分浏览器(Safari 和 Internet Explorer 除外)将文档级节点 Window、Document 和 Document.body 上的 wheel、mousewheel、touchstart 和 touchmove 事件的 passive 默认值更改为 true。
  • 参考:

React性能优化

  1. map循环展示添加key
  2. 路由懒加载
  3. 第三方依赖按需引入
  4. 使用memo或者pureComponent避免不必要的渲染
  5. 合理使用useMemo、memo、useCallback

参考


懒加载与按需加载

懒加载

  • 概念: 可以按照路由对产物进行分割
  • 实现
    import { lazy } from 'react';
    
    const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
    
    <Suspense fallback={<Loading />}>
      <h2>Preview</h2>
      <MarkdownPreview />
    </Suspense>
    
  • 代码二次分割:
    • 可以将那些在非首页才用到的依赖是不是可以单独分割为一个chunk,这样的话可以进一步减少index-[hash].js的体积
    • 需要配合路由懒加载才能达到效果,否则即使代码进行了分割,但加载首屏还是会因为路由提前注册的原因,使得浏览器加载所有资源。
  • 缺点:
    • 优化也有成本,比如文本中虽然通过代码分割+路由懒加载减少了首屏资源体积,但随之也带来了资源请求数增加的问题,对浏览器的并发请求带来了压力。

按需加载

  • 按需加载是为了减少依赖产物体积过大的问题,典型的如 lodash、echarts、arco-design,整个包存在很多方法、组件,而在项目实际使用到的其实只有一部分。在构建时进行按需加载,可以有效减小最终产物体积。
  • 示例:

参考


虚拟滚动

定义

  • 浏览器视口固定,内容响应用户鼠标滚动

使用场景

  • 炫酷的滚动交互
  • 无限循环列表滚动
  • 关注控制滚动体验(灵敏度、惯性等)

如何优化解决虚拟滚动中滚速过快情况下的白屏问题

wip

参考


防抖和节流

防抖

  • 在触发频率高的事件中,执行耗费性能的操作,连续操作后只有最后一次生效
    //自己实现debounce
    function debounce(func,wait=0){
      let timeId = null; //为什么不在内部声明id?因为我如果在内部声明,每一次触发事件就会有一个新的变量产生
      return function(...args){
        const self=this;
        if(timeId){
          clearTimeout(timeId);
        }
        timeId=setTimeout(()=>{
          func.apply(self,args);
        },wait)
      };
    }
    

节流

  • 在触发频率高的事件中,执行耗费性能操作,连续触发,单位时间内只有一次生效
    function throttle(func,wait=0){
      let timeId;
      return function(...args){
        const self=this;
        if(!timeId){
          func.apply(self,args);
          timeId = setTimeout(()=>{
            timeId = undefined;
          },delay);
        }
      };
    }
    

参考


回流与重绘

浏览器渲染过程

  1. 解析 HTML 和样式计算 (parsing and style calculation)
  2. 布局 (Layout)
  3. 绘制 (paint)
  4. 合成 (compositing)

回流

  • reflow
  • 网页渲染引擎根据元素的尺寸、位置和显示属性来重新计算页面的排版和布局,是网页渲染过程中的一个重要步骤。
  • 当一个元素的长与宽改变,可能会影响到画面中其他元素的编排,所以每当有一个元素的布局改变,浏览器的 CPU 需要重新计算整个页面中不同元素的长宽、间距等。在计算的期间,会没办法处理其他任务 (例如使用者在点击按钮,可能会要等一阵子才有回应,因为浏览器无暇处理)。

重绘

  • repaint
  • 指网页渲染引擎根据显示属性 (如颜色、文字大小等) 重新绘制页面元素,不影响元素的位置和尺寸
  • 不会影响页面布局,但是也会影响页面的性能

减少回流和重绘

  1. 移动调整元素时,使用transform
  2. 使用opacity来改变元素的能见度
  3. 避免频繁用 JavaScript 操作 DOM 节点

参考


内存泄漏

定义

  • 内存泄漏可以定义为程序不再使用或不需要的一块内存,但是由于某种原因没有被释放仍然被不必要的占有。

垃圾回收算法

  • 引用计数: 如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
    • 缺点: 循环引用
  • 标记清除
    • 过程
      1. 垃圾回收器创建了一个“roots”列表。Roots通常是代码中全局变量的引用。JavaScript中,“window”对象是一个全局变量,被当作root。window对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);
      2. 所有的 roots被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从root开始的所有对象如果是可达的,它就不被当作垃圾。
      3. 所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。
    • 缺点: 算法运行时程序执行被暂停。

常见的内存泄漏

  1. 全局变量
  2. 被遗忘的定时器和回调函数
  3. DOM引用
  4. 错误使用的闭包

排查

  • 启用use strict
  • 浏览器开发者工具,内存选项

参考

前端监控系统

wip

白屏检测

参考


场景题

线上的某个页面加载慢,如何优化

wip

场景题: 针对大批量的数据的优化

  • 问题: 如果data数据很大,可能导致内存溢出和Minor GC阻塞渲染进程的问题。垃圾回收过程会占用主线程资源,导致渲染进程被阻塞。
  • 思想:
    • 将数据分成多个较小的批次进行处理,而不是一次性处理整个数据集。这可以通过使用setTimeoutrequestAnimationFrame来分批处理数据。循环处理每个批次的数据,并在处理完一个批次后延迟一小段时间处理下一个批次.比如可以将数据分成每批100条,每批处理完后延迟10毫秒,以避免阻塞主线程。
    • processBatch函数用于处理一批数据。它会根据当前的currentIndex从keys数组中选择一批数据进行处理,并将结果存入arr数组。然后,currentIndex增加,检查是否还有剩余数据需要处理。如果还有剩余数据,则通过requestAnimationFrame调度下一个异步任务来处理下一批数据。如果所有数据处理完成,则进行后续操作。
    • 使用递归调用和requestAnimationFrame可以确保数据处理过程在异步任务中进行,在每一帧中调用 processBatch 函数,给渲染进程更多的时间进行渲染
    • 一旦处理完所有数据,调用 finishProcessing 函数,并进行视图更新
  • 代码:
processTreeData(data) {
    const keys = Object.keys(data);
    let arr = [];
    const batchSize = 100; // 每批处理的数据量
    let currentIndex = 0;
    const processBatch = () = >{
        const batchKeys = keys.slice(currentIndex, currentIndex + batchSize);
        const batchItems = batchKeys.flatMap((key) = >{
            const tableData = data[key];
            const parentId = `$ {key}. * `;

            const childItems = tableData.map((item) = >({
               id: `$ {key}.$ {item}`,
               label: `$ {key}.$ {item}`,
               parent: parentId
            }));

            return [{
                id: parentId,
                label: key,
                children: childItems
            }];
        });

        arr.push(...batchItems);

        currentIndex += batchSize;

        if (currentIndex < keys.length) {
            requestAnimationFrame(processBatch);
        } else {
            this.finishProcessing(arr);
            arr = [];
        }
    };
    requestAnimationFrame(processBatch);
},

finishProcessing(arr) {
   //finish operate
},
  • 好处:
    • 如果一项任务执行花费的时间过长,浏览器将无法执行其他任务,例如处理用户事件。这样就会让用户觉得卡顿
    • 可以显示进度指示
  • 思路二:
    • 对于不应该阻塞事件循环的耗时长的繁重计算任务,我们可以使用 Web Workers。
    • Web Workers 可以与主线程交换消息,但是它们具有自己的变量和事件循环。
  • 参考

项目中的分片上传,如何实现的?

wip

怎么做到组件的按需引入

  • treeshaking

优化图片加载闪烁