前端优化手段

29 阅读7分钟

加载时

加载http请求

如果服务器支持HTTP2,那么HTTP2的新特性多路复用(MultiPlexing)能够有效的解决这个问题。

Web 前端 80% 的响应时间花在图片、样式、脚本等资源下载上。

一个 HTTP 请求要经历一个复杂的流程: DNS查找 – TCP握手(三次握手) – 浏览器发出HTTP请求 – 服务器接收请求 – 服务器处理请求和响应结果 – 浏览器接收请求 – TCP断开连接(四次挥手)。

因为一个HTTP请求就要经历这上述等一系列复杂的过程,这还是在不考虑网络环境因素的情况下的,所以当请求比较多时,这就直接体现在性能的消耗上了,这也就是为什么要将多个小文件合并成一个大文件,减少HTTP请求次数的重要原因了。

减少重定向

尽量避免使用重定向,当页面发生了重定向,就会延迟整个HTML文档的传输。在HTML文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载,降低了用户体验。

如果一定要使用重定向的话,如http重定向到https,要使用301永久重定向,而不是302临时重定向,因为如果使用302则每一次访问http都会重定向到https页面,而永久重定向在第一次从http重定向到https之后,每次访问http,会直接返回https的页面。

使用服务端渲染

客户端渲染经历流程:获取HTML文件 – 按需要再获取JS文件 – 运行文件 – 生成DOM – 将DOM插入HTML中 – 最后再渲染页面。

服务端渲染流程:只要获取识别解析HTML文件即可。而且服务端渲染还能有效增强站点的SEO。

静态资源使用CDN

什么是CDN:内容分发网络。它是一组分布在多个不同地理位置的Web服务器。我们都知道,当服务器离用户越远,延迟就越高,CDN就是为了解决这一问题,在多个位置布置服务器,让用户离服务器更近,缩短请求时间。

使用http缓存

缓存针对的是第二次请求,缓存主要是在内存和硬盘。

HTTP缓存主要分为强缓存和协商缓存。

  • 强缓存:在过期日期未到时,不需要请求服务器,像饮料的过期日期一样,状态码是灰色的200。
  • 协商缓存:浏览器和服务器通过标识进行协商,标识匹配上,就让浏览器直接读缓存,标识匹配不上就返回新资源,状态码为304。

图片优化

  1. 图片懒加载。原理是先不给图片设置路径,只有当图片进入可视区域内才去设置。
  2. 图片压缩质量。其实60%~100%质量的图片通常是看不出来区别的。
  3. 用CSS3里样式、动画替换一些图片效果。如小三角形、小箭头、一些简单的gif动画,因为通常代码的质量是图片质量的几分之一或者几十分之一。
  4. 一些图标可以使用雪碧图。

其他方式

  1. 第三方库可以单独提取独立文件,UI框架组件按需加载。
  2. 减少ES6转换成ES5的冗余代码。3
  3. 压缩JS与CSS代码。
  4. 删除无用代码console/alert/注解等。
  5. 启用GZIP。

运行时

避免大量的DOM元素与DOM深度嵌套

大量的DOM元素与深层镶嵌的DOM层级都会造成HTML解析过慢,我们要尽量避免不必要的DOM元素与深层镶嵌的出现。

可以通过如下API获取页面存在的DOM数量:

document.getElementsByTagName('*').length

减少回流和重绘

回流必将引起重绘,重绘不一定引起回流。

  1. JS修改样式时,最好不要直接写样式,而是替换class来改变样式。
  2. JS操作DOM时,可以把DOM元素脱离文档流,可以借助隐藏元素(display:none)或者文档碎片。

webpack相关的性能优化

打包公共代码

使用CommonChunkPlugin插件,将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存到缓存区中供后续使用,这回带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中抽取出来,而不是每次访问一个页面的时候,都需要去加载一个很大的文件。

webpack 4 将移除 CommonsChunkPlugin, 取而代之的是两个新的配置项 optimization.splitChunks 和 optimization.runtimeChunk ,通过设置 optimization.splitChunks.chunks: “all” 来启动默认的代码分割配置项。

动态导入和按需加载

webpack提供了两种技术通过模块内联函数用来分离代码,优先选择的方式是ECMAScript提案的import()语法,第二种则是使用webpack特定的require.ensure。

删除无用的代码

tree shaking是一个术语,通常用于移除Javascripy上下文中的未引用代码,它依赖于ES2015模块系统中的静态结构特性,例如import和export,js的tree shaking主要通过uglifyjs来完成,css的tree shaking通过purify css来实现。

长缓存优化

  1. 将hash替换成chunkhash,这样当chunk不变的时候,缓存依然有效
  2. 使用Name而不是id

每个 module.id 会基于默认的解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变。

下面来使用两个插件解决这个问题。第一个插件是 NamedModulesPlugin,将使用模块的路径,而不是数字标识符。虽然此插件有助于在开发过程中输出结果的可读性,然而执行时间会长一些。第二个选择是使用 HashedModuleIdsPlugin,推荐用于生产环境构建。

公共代码内联

使用html-webpack-inline-chunk-plugin插件将manifest.js内联到html文件中。

首屏加载优化

优化思路:

  1. 将页面内容尽快地展示给用户,减少页面白屏时间。
  2. 将用户可操作的时间尽量提前,避免用户无法操作的卡顿体验。

减少白屏时间除了我们常说的首屏加载耗时优化,还可以考虑使用一些过渡的动画,让用户感知到页面正在顺利加载,从而避免用户对于白屏页面或是静止页面产生烦躁和困惑。除了技术侧的优化,很多时候产品策略的调整,给用户带来的体验优化效果不低于技术手段优化,因此我们也需要重视。

一般来说,我们需要尽可能地降低首屏需要的代码量和执行耗时,可以通过以下方式进行:

  • 对页面的内容进行分片/分屏加载
  • 仅加载需要的资源,通过异步或是懒加载的方式加载剩余资源
  • 使用骨架屏进行预渲染
  • 使用差异化服务,比如读写分离,对于不同场景按需加载所需要的模块
  • 使用服务端直出渲染,减少页面二次请求和渲染的耗时

有些时候,我们的页面也需要在客户端进行展示,此时可充分利用客户端的优势:

  • 配合客户端进行资源预请求和预加载,比如使用预热 Web 容器
  • 配合客户端将资源和数据进行离线,可用于下一次页面的快速渲染
  • 使用秒看技术,通过生成预览图片的方式提前将页面内容提供给用户

除了首屏渲染以外,用户在浏览器页面过程中,也会触发页面的二次运算和渲染,此时需要进行渲染过程的优化。

渲染过程优化

我们可以将其理解为首屏加载完成后,用户的操作交互触发的二次渲染。

主要思路是减少用户的操作等待时间,以及通过将页面渲染帧率保持在 60FPS 左右,提升页面交互和渲染的流畅度。包括但不限于以下方案:

  • 使用资源预加载,提升空闲时间的资源利用率
  • 减少/合并 DOM 操作,减少浏览器渲染过程中的计算耗时
  • 使用离屏渲染,在页面不可见的地方提前进行渲染(比如 Canvas 离屏渲染)
  • 通过合理使用浏览器 GPU 能力,提升浏览器渲染效率(比如使用 css transform 代替 Canvas 缩放绘制)