前端性能优化

458 阅读10分钟

性能是前端开发一个非常重要的组成部分,前端性能优化也是一个永恒的话题。本文章从浏览器打开页面的五个简单步骤中进行了前端性能优化方法简单梳理。并且结合参与的项目开发具体介绍了三种优化的方法。分别是1.使用打包工具,进行路由懒加载2.资源压缩Gzip3.利用CDN减少响应时间。

前端性能优化.jpg

一、使用打包工具,进行路由懒加载

(1)webpack编译和构建流程

webpack中负责构建和编译都是Compilation中。

在webpack make钩子中, tapAsync注册了一个DllEntryPlugin, 就是将入口模块通过调用compilation.addEntry方法将所有的入口模块添加到编译构建队列中,开启编译流程。

随后在addEntry 中调用_addModuleChain开始编译。

_addModuleChain首先会生成模块,最后构建。

在编译完成后,调用compilation.seal方法封闭,生成资源,这些资源保存在compilation.assets, compilation.chunk, 在给webpack写插件的时候会用到。

seal执行后,便会调用emit钩子,根据webpack config文件的output配置的path属性,将文件输出到指定的path.

(2)路由懒加载原理分析

1.准备工作

路由懒加载也可以叫做路由组件懒加载,最常用是通过import()实现。然后通过Webpack编译打包后,会把每个路由组件的代码分割成一一个js文件,初始化时不会加载这些js文件,只当激活路由组件才会去加载对应的js文件。

carbon 1.png

项目中的简单例子

carbon 2.png

利用webpackChunkName使编译打包后的js文件名字能和路由组件一一对应,修改一下load函数。

carbon 3.png

去掉代码压缩混淆,便于我们阅读编译打包后的代码。在vue.config.js中配置

carbon 4.png

2.分析index.html

路由组件编译打包后对应的js文件

carbon 5.png

  • ref=preload:告诉浏览器这个资源要给我提前加载。
  • rel=prefetch:告诉浏览器这个资源空闲的时候给我加载一下。
  • as=script:告诉浏览器这个资源是script,提升加载的优先级。
3.分析chunk-xxx.xxx.js

从代码中可以看出,执行chunk-xxx.xxx.js,仅仅把下面这个数组pushwindow["webpackJsonp"]中,而数组第二项是个对象,对象的每个value值是一个函数表达式,不会执行。

carbon 6.png

4.分析app.xxx.js

app.xxx.js里面是一个自执行函数,通过搜索window["webpackJsonp"]可以找到如下相关代码。

carbon 7.png 先把window["webpackJsonp"]赋值给jsonpArray

jsonpArraypush方法赋值给oldJsonpFunction

webpackJsonpCallback函数拦截jsopArraypush方法,也就是说调用window["webpackJsonp"]push方法都会执行webpackJsonpCallback函数。

jsonpArray浅拷贝一下再赋值给jsonpArray

因为执行chunk-xxx.js中的window["webpackJsonp"].pushpush方法还未被webpackJsonpCallback函数拦截,所以要循环jsonpArray,将每项作为参数传入webpackJsonpCallback函数并调用。

jsonpArraypush方法再赋值给parentJsonpFunction

5.具体功能函数

webpackJsonpCallback函数

checkDeferredModules函数

_webpack_require函数

webpackAsyncContext函数

webpack_require.e方法

__webpack_require__.e方法是实现懒加载的核心,在这个方法里面处理了三件事情。

  • 使用JSONP模式加载路由对应的js文件,也可以称为chunk。
  • 设置chunk加载的三种状态并缓存在installedChunks中,防止chunk重复加载。
  • 处理chunk加载超时和加载出错的场景。

chunk加载的三种状态

  • installedChunks[chunkId]0,代表该chunk已经加载完毕。
  • installedChunks[chunkId]undefined,代表该chunk加载失败、加载超时、从未加载过。
  • installedChunks[chunkId]Promise对象,代表该chunk正在加载。

二、资源压缩Gzip

什么是Gzip:gzip是一种数据的压缩格式,或者说是一种文件格式。

Gzip原本用户UNIX系统的文件压缩,后来逐渐成为Internet最主流的数据压缩格式。 当用户访问我们的web站点时,服务器就将我们的网页文件进行压缩,将压缩后的文件传输到客户端,对于纯文本文件我们可以至少压缩到原大小的40%,这样大大提高了传输效率,页面便可更快的加载出来。

1.Nginx服务端配置Gzip服务

由于目前我们项目是使用ngxin来部署前端的,nginx自带HttpGzip模块 所以我们直接对nginx.conf文件的http配置项进行配置即可。但相对的由于nginx自身处理请求然后压缩返回,会消耗对应的服务器内存。

    gzip on;
    
    # 允许压缩的页面最小字节数, 默认值是0,不管页面多大都压缩。建议设置成大于1k的字节数,小于1k可能会越压越大
    gzip_min_length 1k; 
    
    # 设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流。例如 4 4k 代表以4k为单位,按照原始数据大小以4k为单位的4倍申请内存。
    gzip_buffers 4 16k; 
    
    # 识别http的协议版本。由于早期的一些浏览器或者http客户端,可能不支持gzip自解压,用户就会看到乱码,所以做一些判断还是有必要的。
    gzip_http_version 1.0;
    
    # gzip压缩比,1 压缩比最小处理速度最快,9 压缩比最大但处理最慢(传输快但比较消耗cpu)。
    gzip_comp_level 2;
    
    # 匹配MIME类型进行压缩,(无论是否指定)"text/html"类型总是会被压缩的。
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
    
    # 和http头有关系,加个vary头,给代理服务器用的
    gzip_vary off;
    
    # 表示IE6及以下禁止压缩
    gzip_disable "MSIE [1-6]\.";

2.客户端压缩Gzip

我们应尽可能减少对服务端内存的使用,毕竟服务端的资源是十分宝贵的,这里我们仍然使用nginx进行前端部署,我们在客户端替nginx处理压缩文件这一步操作,nginx便可直接使用我们压缩好的文件。项目需要安装好webpack插件npm install compression-webpack-plugin@5.0.1 -D并且配置vue.config.js注册插件,然后运行打包命令。安装nginx并且修改nginx.conf启动nginx服务

carbon 修改nginx.png

三、利用CDN减少响应时间

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

CDN 原理

当用户访问一个网站时,如果没有 CDN,过程是这样的:

  1. 浏览器要将域名解析为 IP 地址,所以需要向本地 DNS 发出请求。
  2. 本地 DNS 依次向根服务器、顶级域名服务器、权限服务器发出请求,得到网站服务器的 IP 地址。
  3. 本地 DNS 将 IP 地址发回给浏览器,浏览器向网站服务器 IP 地址发出请求并得到资源。

通过CDN获取缓存内容的过程

如图所示是通过CDN进行请求响应的过程图。通过图中可以看出,在DNS解析域名时新增了一个全局负载均衡系统(GSLB),GSLB的主要功能是根据用户的本地DNS的IP地址判断用户的位置,筛选出距离用户较近的本地负载均衡系统(SLB),并将该SLB的IP地址作为结果返回给本地DNS。SLB主要负责判断缓存服务器集群中是否包含用户请求的资源数据,如果缓存服务器中存在请求的资源,则根据缓存服务器集群中节点的健康程度、负载量、连接数等因素筛选出最优的缓存节点,并将HTTP请求重定向到最优的缓存节点上。

img

为了更清晰地说明CDN的工作原理,下面以客户端发起对"join.qq.com/video.php"的HTTP请求为例进行说明:

  1. 用户发起对"join.qq.com/video.php"的HTTP请求,首先需要通过本地DNS通过"迭代解析"的方式获取域名"join.qq.com"的IP地址;
  2. 如果本地DNS的缓存中没有该域名的记录,则向根DNS发送DNS查询报文;
  3. 根DNS发现域名的前缀为"com",则给出负责解析com顶级DNS的IP地址;
  4. 本地DNS向顶级DNS发送DNS查询报文;
  5. 顶级DNS发现域名的前缀为"qq.com",在本地记录中查找负责该前缀的权威DNS的IP地址并进行回复;
  6. 本地DNS向权威DNS发送DNS查询报文;
  7. 权威DNS查找到一条NAME字段为"join.qq.com"的CNAME记录(由服务提供者配置),该记录的Value字段为"join.qq.cdn.com";并且还找到另一条NAME字段为"join.qq.cdn.com"的A记录,该记录的Value字段为GSLB的IP地址;
  8. 本地DNS向GSLB发送DNS查询报文;
  9. GSLB根据本地DNS的IP地址判断用户的大致位置为深圳,筛选出位于华南地区且综合考量最优的SLB的IP地址填入DNS回应报文,作为DNS查询的最终结果;
  10. 本地DNS回复客户端的DNS请求,将上一步的IP地址作为最终结果回复给客户端;
  11. 客户端根据IP地址向SLB发送HTTP请求:"join.qq.com/video.php";
  12. SLB综合考虑缓存服务器集群中各个节点的资源限制条件、健康度、负载情况等因素,筛选出最优的缓存节点后回应客户端的HTTP请求(状态码为302,重定向地址为最优缓存节点的IP地址);
  13. 客户端接收到SLB的HTTP回复后,重定向到该缓存节点上;
  14. 缓存节点判断请求的资源是否存在、过期,将缓存的资源直接回复给客户端,否则到源站进行数据更新再回复。

其中较为关键的步骤为6~9,与普通的DNS过程不同的是,这里需要服务提供者(源站)配置它在其权威DNS中的记录,将直接指向源站的A记录修改为一条CNAME记录及其对应的A记录,CNAME记录将目标域名转换为GSLB的别名,A记录又将该别名转换为GSLB的IP地址。通过这一系列的操作,将解析源站的目标域名的权力交给了GSLB,以致于GSLB可以根据地理位置等信息将用户的请求引导至距离其最近的"缓存节点",减缓了源站的负载压力和网络拥塞。

以上主要介绍了目前CDN中最为常见的工作方式,这种工作方式利用CNAME将域名和目标IP之间进行解耦,将目标IP的解析权下放到GSLB中,方便实现更多自定义的功能,是一种更加灵活的方式。

参考文章:

webpack详情

前端性能优化

路由懒加载的原理

前端性能优化之Gzip

配置nginx直接使用webpack生成的gz压缩文件

半小时搞懂HTTP、HTTPS和HTTP2

前端性能优化24条建议

CDN原理解析\