前端性能优化

463 阅读6分钟

前端性能优化,无非两点:

  • 提升资源请求速度
  • 提升界面渲染速度

所以我们在设计系统或编写代码的时候可以采取一些策略,以便于优化用户体验。例如,资源压缩合并树摇、懒加载、预加载、加载动画和骨架屏、图片以base64或icons展示、接口优化、gzip等。

具体有以下几个优化方向:

  1. 减小资源大小
  2. 减少资源请求数量
  3. 避免回流与重绘
  4. 优化高频事件
  5. 缓存公用数据
  6. 按需加载
  7. 预加载

webpack配置

构建优化

缓存

webpack5

cache: {
  type: 'filesystem',
  buildDependencies: {
    config: [ __filename ],
  },
},

DllPlugin

多线程

  • happypack

缩小查找范围

  • exclude/include

输出优化

  • splitChunks - 代码合并压缩、抽取共用部分
  • tree shaking - 删除不需要的额外代码
  • 按需加载

从输入URL到页面展示,我们需要了解的优化点。

从输入URL到页面展示过程

  1. url解析
  2. dns解析
  3. tcp建立连接
  4. 发起http请求
  5. 服务器响应
  6. 浏览器解析并渲染
  7. 页面加载完成

浏览器缓存

协商缓存
  • Last-Modified/If-Modified-Since:第一次请求,respone的header加上Last-Modified(最后修改时间),再次请求,在request的header上加上If-Modified-Since
  • Etag/If-None-Match:这两个值是由服务器生成的每个资源的唯一标识字符串,只要资源有变化就这个值就会改变;其判断过程与Last-Modified/If-Modified-Since类似,他可以精确到秒的更高级别。

协商缓存意思是文件已经被缓存了,但是否从缓存中读取是需要和服务器进行协商,具体如何协商要看请求头/响应头的字段设置。需要注意的是协商缓存还是发了请求的。

强制缓存
  • Expires: 绝对时间,判断客户端日期是否超过这个时间
  • Cache-Control:相对时间,判断访问间隔是否大于3600秒

强制缓存就是文件直接从缓存中获取,不需要发送请求。

  • Cache-Control 响应头表示了资源是否可以被缓存,以及缓存的有效期。
  • Etag 响应头标识了资源的版本,此后浏览器可据此进行缓存以及询问服务器。
  • Last-Modified 响应头标识了资源的修改时间,此后浏览器可据此进行缓存以及询问服务器。
什么时候存dist,什么时候存memoey?
  • 大一点的文件会缓存在dist里面,因为内存也是有限的,磁盘的空间更大
  • 小一点文件js,图片存的是memory
  • css文件一般存在dist
  • 特殊情况memory大小是有限制的,浏览器也会根据自己的内置算法,把一部分js文件存到dist里面
使用http2
  • 多路复用
  • 首部压缩

页面渲染

  • 解析HTML生成DOM树。
  • 解析CSS生成CSSOM规则树。
  • 将DOM树与CSSOM规则树合并在一起生成渲染树。
  • 遍历渲染树开始布局,计算每个节点的位置大小信息。
  • 将渲染树每个节点绘制到屏幕。

重排重绘

回流/重排

回流主要指几何属性需要需要改变的渲染。渲染树节点的几何属性发生改变,导致其位置发生改变 ,从而重新计算生成新的渲染树。这就是回流为什么消耗性能的主要原因,因为他重新计算了生成了DOM。

重绘

重绘指外观属性的更改。他影响了节点外观属性的变化,但并不会影响其几何属性。但是几何属性的改变一定会影响外观属性的变化。

什么操作会导致重排?
  • 添加或删除可见的 DOM 元素
  • 元素位置改变
  • 元素尺寸改变
  • 内容改变
  • 浏览器窗口尺寸改变
如何减少重排重绘?
  • 用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。
  • 如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。
  • 使用transform translate设置位置。

内存管理

进程与线程

  • 进程: 程序的一次执行,他占有一片独有的内存空间,是操作系统的基本单位。

    • 一个进程中至少有一个运行的线程:主线程。进程启动后自动创建。
    • 一个进程中也可以同时运行多个线程,程序是多线程运行的。
    • 一个进程内的数据可以提供其中的多个线程共享,多个进程之间的数据是不能直接共享的。
  • 线程: 是建成内一个独立执行的单位,是CPU调度的最小单元,程序运行的基本单位。

  • 线程池:保存多个线程对象的容器,实现线程对象的反复利用。

js为什么是单线程

JavaScript采用单线程,是为了避免在执行过程中页面内容被不可预知的重复修改。

在JavaScript运行一段代码块的时候,页面中其他的事情(UI更新或者别的脚本加载执行等)在同一时间段内是被挂起的状态,不能被同时处理的,所以在执行一段js脚本的时候,这段代码会影响其他的操作。

垃圾回收

JavaScript中会被判定为垃圾:

  • 对象不再被引用是垃圾
  • 对象不能从根上访问到是垃圾

内存优化

  1. 慎用全局变量
  2. 避开闭包陷阱
  3. 选择最优的循环方法
  4. 垃圾回收
  5. 减少嵌套
  6. 尾递归优化

性能检测

LightHouse

其它

节流和防抖

防抖:在设定时间内只执行最后一次事件,前面的事件被clear掉


const debounce=(func,wait=60)=>{
  let timer=null;
  return function (...args){
    clearTimeout(timer);
    timer=setTimeout(()=>func.apply(this,args),wait);
  };
};

节流:在设定时间内只执行第一次事件,执行后将当前时间设为开始时间


const throttle=(func,delay=60)=>{
  let start=0;
  return function (...args){
    const current=+new Date();
    if(current-start>delay){
      func.apply(this,args);
      start=current;
    }
  };
};

加载动画和骨架屏

避免页面卡顿或闪烁。

图片优化

雪碧图、base64、icons...

尾递归

若函数在尾位置调用自身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。优化调用栈,节约内存。

memoize

const memoize=(fn,len=100)=>{
  let cache=[];
  return (...args)=>{
    const key=JSON.stringify(args);
    const cached=cache.find(v=>v.key===key);
    if(!cached){
      const result=fn(...args);
      cache.push({key,result});
      if(cache.length>len){
        cache.shift();
      }
      return result;
    }
    return cached.result;
  };
};

虚拟列表

只渲染可视窗口内容。

react优化

  • 缩小依赖范围
  • 避免派生 state
  • 使用immutable数据
  • 使用useMemo

关注我

微信搜索“前端道萌”