前端面试题 - 12. 前端性能优化有哪些方法

243 阅读9分钟

按照过程先细分下场景:

  1. 页面加载的性能
  2. 页面渲染的性能
  3. 用户交互的性能

1. 加载性能优化

1.1 网络优化

1.1.1 使用DNS预解析

<link rel="dns-prefecth" href="https://www.google.com">
<link rel="dns-prefecth" href="https://www.google-analytics.com">

当浏览器访问一个域名的时候,需要解析一次DNS,获得对应域名的ip地址。在解析过程中,按照浏览器缓存、系统缓存、路由器缓存、ISP(运营商)DNS缓存、根域名服务器、顶级域名服务器、主域名服务器的顺序,逐步读取缓存,直到拿到IP地址

DNS Prefetch,即DNS预解析就是根据浏览器定义的规则,提前解析之后可能会用到的域名,使解析结果缓存到系统缓存中,缩短DNS解析时间,来提高网站的访问速度。由于它是并行的,不会堵塞页面渲染,这样可以缩短资源加载的时间

1.1.2 使用CDN

CDN全称是Content Delivery Network,即内容分发网络,它能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。

  1. 本地 DNS 再向 GSLB 发出请求,GSLB 的主要功能是根据本地 DNS 的 IP 地址判断用户的位置,筛选出距离用户较近的本地负载均衡系统(SLB),并将该 SLB 的 IP 地址作为结果返回给本地 DNS。
  2. 本地 DNS 将 SLB 的 IP 地址发回给浏览器,浏览器向 SLB 发出请求。
  3. SLB 根据浏览器请求的资源和地址,选出最优的缓存服务器发回给浏览器。
  4. 浏览器再根据 SLB 发回的地址重定向到缓存服务器。

1.1.3 并发网络连接

  • HTTP1.1协议下,chrome每个域名的最大并发数是6个。使用多个域名,可以增加并发数。
  • http1.1开启 keep-alive 持久连接,一个连接多次请求。
  • HTTP2协议中,可以开启管道化连接,即单条连接的多路复用。

1.2 资源优化

1.2.1 减少资源请求

  • 合并:将完整的资源尽量合并(降低断开、插入的风险),当然也不宜过大。

  • 图片:字体图标、css雪碧图、base64内嵌到html(内容增加1/3)。

  • 减少重定向:重定向html就延迟返回,其他资源也不会加载。如果一定要重定向就使用301永久重定向,避免302临时重定向每次都需要重定向。

  • 缓存:使用cach-control或expires这类强缓存时,缓存不过期的情况下,不向服务器发送请求。强缓存过期时,会使用last-modified或etag这类协商缓存,向服务器发送请求,如果资源没有变化,则服务器返回304响应,浏览器继续从本地缓存加载资源;如果资源更新了,则服务器将更新后的资源发送到浏览器,并返回200响应。

  • css:不用@import,CSS的@import会造成额外的请求。

  • 避免使用空的src和href:会重定向到当前的页面地址。会提交表单到当前的页面地址。

1.2.2 减少资源大小

  • http协议开启gzip一般www服务器都有压缩功能,将资源压缩后传输,纯文本能降到到40%
  • css/html/js/图片 这些资源压缩,可以借助webpack的一些混淆压缩插件做。

1.2.3 优化资源加载顺序

  • 资源加载位置:通过优化资源加载位置,更改资源加载时机

1、CSS文件放在head中,先外链,后本页

2、JS文件放在body底部,先外链,后本页

3、处理页面兼容、处理页面布局的JS文件放在head中,如babel-polyfill.js文件、flexible.js文件

4、body中间尽量不写style标签和script标签

  • 资源加载时机

1、异步加载script标签

defer: 异步加载,在HTML解析完成后执行。defer的实际效果与将代码放在body底部类似

async: 异步加载,加载完成后立即执行

2、模块按需加载

在SPA等业务逻辑比较复杂的系统中,需要根据路由来加载当前页面需要的业务模块

webpack优先支持ES的import() 语法。也有自己实现的require.ensure。

3、使用资源预加载preload和资源预读取prefetch

preload让浏览器提前加载指定资源,需要执行时再执行,可以加速本页面的加载速度

prefetch告诉浏览器加载下一页面可能会用到的资源,可以加速下一个页面的加载速度

4、资源懒加载与资源预加载

资源延迟加载也称为懒加载,延迟加载资源或符合某些条件时才加载某些资源。

2. 渲染性能优化

2.1 css

  1. 避免使用层级较深的选择器,或其他一些复杂的选择器,以提高CSS渲染效率
  2. 避免使用CSS表达式,CSS表达式是动态设置CSS属性的强大但危险方法,它的问题就在于计算频率很快。不仅仅是在页面显示和缩放时,就是在页面滚动、乃至移动鼠标时都会要重新计算一次
  3. 元素适当地定义高度或最小高度,否则元素的动态内容载入时,会出现页面元素的晃动或位置,造成回流
  4. 给图片设置尺寸。如果图片不设置尺寸,首次载入时,占据空间会从0到完全出现,上下左右都可能位移,发生回流
  5. 不要使用table布局,因为一个小改动可能会造成整个table重新布局。而且table渲染通常要3倍于同等元素时间
  6. 能够使用CSS实现的效果,尽量使用CSS而不使用JS实现
  7. 使用 GPU 加速:可以使用 CSS3 的 transform 和 animation 属性来实现 GPU 加速,提高页面的渲染性能。 以上是一些交互方面的性能优化方法,可以根据具体的业务场景选择合适的优化方案,提高页面性能和用户体验。

2.2 渲染

  1. 此外,将需要多次重绘的元素独立为render layer渲染层,如设置absolute,可以减少重绘范围
  2. 对于一些进行动画的元素,使用硬件渲染,从而避免重绘和回流

2.3 dom

  1. 缓存DOM变量

由于查询DOM比较耗时,在同一个节点无需多次查询的情况下,可以缓存DOM

  1. 减少DOM深度及DOM数量

HTML 中标签元素越多,标签的层级越深,浏览器解析DOM并绘制到浏览器中所花的时间就越长,所以应尽可能保持 DOM 元素简洁和层级较少

  1. 批量操作DOM

由于DOM操作比较耗时,且可能会造成回流,因此要避免频繁操作DOM,可以批量操作DOM,先用字符串拼接完毕,再用innerHTML更新DOM

  1. 批量操作CSS样式

通过切换class或者使用元素的style.csstext属性去批量操作元素样式

  1. 在内存中操作DOM

使用DocumentFragment对象,让DOM操作发生在内存中,而不是页面上

  1. DOM元素离线更新

对DOM进行相关操作时,例、appendChild等都可以使用Document Fragment对象进行离线操作,带元素“组装”完成后再一次插入页面,或者使用display:none 对元素隐藏,在元素“消失”后进行相关操作

  1. DOM读写分离

浏览器具有惰性渲染机制,连接多次修改DOM可能只触发浏览器的一次渲染。而如果修改DOM后,立即读取DOM。为了保证读取到正确的DOM值,会触发浏览器的一次渲染。因此,修改DOM的操作要与访问DOM分开进行

  1. 及时清理环境

及时消除对象引用,清除定时器,清除事件监听器,创建最小作用域变量,可以及时回收内存

3. 交互性能优化

3.1 减少 DOM 操作

频繁的 DOM 操作会引起浏览器的重排和重绘,导致页面性能下降。

  • 通过缓存 DOM 对象
  • 合并 DOM 操作
  • 使用 DocumentFragment 等方式来减少 DOM 操作次数。

3.2 减少重排和重绘

重排和重绘是浏览器性能瓶颈之一。

  • 避免频繁修改 DOM 和样式
  • 使用 transform 和 opacity 属性来代替 top、left、width 和 height 属性等方式来减少重排和重绘次数,提高页面性能。

3.3 使用事件委托

事件代理是指将事件监听器注册在父级元素上,由于子元素的事件会通过事件冒泡的方式向上传播到父节点,因此,可以由父节点的监听函数统一处理多个子元素的事件

利用事件代理,可以减少内存使用,提高性能及降低代码复杂度

3.4 使用 Web Worker

Web Worker 可以在浏览器中创建多个线程,使得 JavaScript 的计算可以在后台执行,不会影响页面的交互和响应速度。

3.5 使用节流和防抖

使用函数节流(throttle)或函数去抖(debounce),限制某一个方法的频繁触发,避免因为频繁的事件触发而引起性能问题。

3.6 使用虚拟列表

对于大量数据的展示,可以使用虚拟列表技术来减少 DOM 渲染次数,提高页面性能。

3.7 懒加载

使用 Intersection Observer API:Intersection Observer API 可以观察元素是否进入或离开可视区域,可以用来实现图片懒加载等优化。

const images = document.querySelectorAll('.lazy-load');
const callback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      const src = img.dataset.src;
      img.src = src;
      observer.unobserve(img);
    }
  });
};
const options = {
  root: null,
  rootMargin: '0px',
  threshold: 0.1
};
const observer = new IntersectionObserver(callback, options);
images.forEach(image => {
  observer.observe(image);
});

参考资源:

前端性能优化七大手段:www.cnblogs.com/SuremoWang/…

perform api:www.wenwoha.com/blog_detail…

有一种优化叫preload:blog.51cto.com/u_15127660/…