我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

栏目: 编程语言 · 发布时间: 5年前

内容简介:马上到了金三银四的时间,很多公司开启了今年第一轮招聘的热潮,虽说今年是互联网的寒冬,但是只要对技术始终抱有热情以及有过硬的实力,即使是寒冬也不会阻挠你前进的步伐。在面试的时候,往往在二面,三面的时面试官会结合你的简历问一些关于你简历上项目的问题,而以下这个问题在很多时候都会被问到其实这个问题旨在了解你在遇到问题的时候的解决方法,毕竟现在前端技术领域广,各种框架和组件库层出不穷,而业务需求上有时纷繁复杂,观察一个程序员在面对未知问题时是如何处理的,这个过程相对于只出一些面试题来考面试者更能了解面试者实际解决问

马上到了金三银四的时间,很多公司开启了今年第一轮招聘的热潮,虽说今年是互联网的寒冬,但是只要对技术始终抱有热情以及有过硬的实力,即使是寒冬也不会阻挠你前进的步伐。在面试的时候,往往在二面,三面的时面试官会结合你的简历问一些关于你简历上项目的问题,而以下这个问题在很多时候都会被问到

在这个项目中你有遇到什么技术难点,你是怎么解决的?

其实这个问题旨在了解你在遇到问题的时候的解决方法,毕竟现在前端技术领域广,各种框架和组件库层出不穷,而业务需求上有时纷繁复杂,观察一个 程序员 在面对未知问题时是如何处理的,这个过程相对于只出一些面试题来考面试者更能了解面试者实际解决问题的能力

而很多人会说我的项目不大,并没有什么难点,或者说并不算难点,只能说是一些坑,只要google一下就能解决,实在不行请教我同事,这些问题并没有困扰我很久。其实我也遇到过相同的情况,和面试官说如何通过搜索引擎解决这些坑的吧不太好,让面试官认为你只是一个API Caller,但是又没有什么值得一谈的项目难点

我的建议是,如果没有什么可以深聊的技术难点,不妨在日常开发过程中,试着封装几个常用的组件,同时尝试分析项目的性能瓶颈,寻找一些优化的方案,同样也能让面试官对你有一个整体的了解

在这篇文章中,我会分享在我目前公司的项目里,是如何在满足业务需求的基础上,让整个系统焕然一新的过程

技术栈是Vue + Element的单页面应用

起源

在我刚入职的那会,编码能力不怎么好,加上之前离职的前端技术栈是React,接手这个Vue项目的时候,代码高度的耦合,而那个时候因为能力有限,也只是在他的基础上继续开发,好在接手的时候开发进度也只是刚刚开始,因此在几个月后的某一天,我做了一个决定:准备把整个项目重写

得益于整个后台管理系统都是我独立开发的,项目的不足点我都深有体会,并且修改的时候能够更加的自由,恰好在那段时间看了花裤衩的vue-element-admin,我决定新开一个工程把之前的代码全部重写

项目结构

之前我有打算基于Webpack4自己写个脚手架用来打包文件,但是那段时间刚好Vue-cli3刚刚发布正式版并且也是基于Webpack4封装的,于是想了一下还决定使用新的Vue-cli3脚手架搭建,最后我将项目分为以下层级

├─api                                 //api接口
├─assets                              //项目运行时使用到的图片和静态资源
├─components                          //组件
│  ├─Breadcrumb                       //面包屑组件
│  ├─Ellipsis                         //业务组件
│  ├─FormPanel                        //业务组件
│  ├─Sidebar                          //侧边栏组件
│  └─globalComponents                 //全局组件
│     ├─Pagination                    //分页器组件   
│     ├─SildeDown                     //业务组件
│     ├─SvgIcon                       //svg图标组件
│     ├─TableOptions                  //业务组件
│     ├─Toggle                        //业务组件
│     ├─ZTable                        //表格组件
│     └─index.js                      //全局组件自动注册的脚本
│  
├─directive                           //自定义指令
├─element                             //elementui
├─errorLog                            //错误捕获
├─filters                             //全局过滤器
├─icons                               //svg图标存放文件夹
├─interface                           //TypeScript接口
├─mixins                              //局部混入
├─router                              //vue-router
│  ├─modules                      
│  └─index.js           
├─store                                //vuex
│  ├─modules                      
│  └─index.js                      
├─style                                //全局样式/局部页面可复用的样式
├─util                                //公共的模块(axios,cookie封装,工具函数)
├─vendor                              //类库文件
└─views                               //页面组件
复制代码

一个良好的项目分层在业务迭代的时候能够快速找到对应的模块进行修改,而不是在茫茫的代码海中找到其中的某一行代码

性能优化

在我重写整个系统之前,每次打包都会花费好几分钟的时间,并且打包后的项目超过了17M

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

然而在我优化系统之后,打包后的体积只有2M,缩小了 8倍

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

这里我从以下4个方面分享一下我在项目中是如何改善系统的性能,让系统"步履如飞"的

  • 构建相关
  • 网络请求相关
  • 静态资源优化
  • 编码相关

构建相关

构建方面通过合理的配置构建工具,达到减少生产环境的代码的体积,减少打包时间,缩短页面加载时间

路由懒加载

传统的路由组件是通过import静态的打包到项目中,这样做的缺点是因为所有的页面组件都打包在同一个脚本文件中,导致生产环境下首屏因为加载的代码量太多会有明显的卡顿(白屏)

通过import()使得ES6的模块有了动态加载的能力,让url匹配到相应的路径时,会动态加载页面组件,这样首屏的代码量会大幅减少,webpack会把动态加载的页面组件分离成单独的一个chunk.js文件

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化
我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

当然懒加载也有缺点,就是会额外的增加一个http请求,如果项目非常小的话可以考虑不使用路由懒加载

预渲染

由于浏览器在渲染出页面之前,需要先加载和解析相应的html,css和js文件,为此会有一段白屏的时间,如何尽可能的减少白屏对用户的影响,目前我选择的是在html模版中,注入一个loading动画,这里我拿D2-Admin中的loading动画举例

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>icon.ico">
    <title><%= VUE_APP_TITLE %></title>
    <style>
      html, body, #app { height: 100%; margin: 0px; padding: 0px; }
      .d2-home { background-color: #303133; height: 100%; display: flex; flex-direction: column; }
      .d2-home__main { user-select: none; width: 100%; flex-grow: 1; display: flex; justify-content: center; align-items: center; flex-direction: column; }
      .d2-home__footer { width: 100%; flex-grow: 0; text-align: center; padding: 1em 0; }
      .d2-home__footer > a { font-size: 12px; color: #ABABAB; text-decoration: none; }
      .d2-home__loading { height: 32px; width: 32px; margin-bottom: 20px; }
      .d2-home__title { color: #FFF; font-size: 14px; margin-bottom: 10px; }
      .d2-home__sub-title { color: #ABABAB; font-size: 12px; }
    </style>
  </head>
  <body>
    <noscript>
      <strong>
        很抱歉,如果没有 JavaScript 支持,D2Admin 将不能正常工作。请启用浏览器的 JavaScript 然后继续。
      </strong>
    </noscript>
    <div id="app">
      <div class="d2-home">
        <div class="d2-home__main">
          <img
            class="d2-home__loading"
            src="./image/loading/loading-spin.svg"
            alt="loading">
          <div class="d2-home__title">
            正在加载资源
          </div>
          <div class="d2-home__sub-title">
            初次加载资源可能需要较多时间 请耐心等待
          </div>
        </div>
        <div class="d2-home__footer">
          <a
            href="https://github.com/d2-projects/d2-admin"
            target="_blank">
            https://github.com/d2-projects/d2-admin
          </a>
        </div>
      </div>
    </div>
  </body>
</html>

复制代码

在打包完成后,在这个index.html下方还会注入页面的脚本,当用户访问你的项目时,脚本还没有执行,但是可以显示loading动画,因为它是直接注入在html中的,等到脚本执行完毕后,Vue会新生成一个app的节点然后将旧的同名节点删除,这样可以有效的过渡白屏的时间

loading动画只是一个让用户感知到你程序正在启动的效果,只是一个静态页面没有任何的功能

另外预渲染还可以使用服务端渲染(SSR),通过后端输出一个首页的模版,或者使用骨架屏的方案,这里本人没有深入的了解过,有兴趣的朋友可以去实践一下

升级到最新的webpack版本

webpack4相对于webpack3来说在打包优化方面性能提升还是比较明显的,如果觉得自己配置脚手架比较复杂的话,可以使用vue-cli3来构建你的项目,同样是基于webpack4搭建的

合理使用第三方库

如果项目中有一些日期操作的需求,不妨将目光从moment转移到 day ,相对于笨重的moment,它只有2kb,day和moment的api完全一样,并且中文文档也比较友好

另外对于lodash这类的库如果只需要部分功能,则只要引入其中一部分,这样webpack在treeshaking后在生产环境也只会引入这一部分的代码

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

常用的路径创建文件别名

给常用的模块路径创建一个别名是一个不错的选择,可以减少模块查找时耗费的时间,项目越大收益也就越明显

vue-cli3中的配置和使用方法(webpack链式调用文档)

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

使用可视化 工具 分析打包后的模块体积

我通过webpack-bundle-analyzer这个插件在每次打包后能够更加直观的分析打包后模块的体积,再对其中比较大的模块进行优化

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

这是我在优化前的各模块体积:

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

因为业务需求,要求前端导出pdf和excel文件,我这里引入了xlsx和pdf.js这2个包,但是打包后通过可视化工具发现光着2个文件就占了一半的项目体积,另外elementui和moment也非常的大

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

这是我优化后通过可视化工具观察到的各模块体积,我将这些类库放到CDN上从生产环境中抽离出去,可以看到没有明显特别大的模块了

网络请求相关

这部分旨在实现需求的前提下尽量减少http请求的开销,或者减少响应时间

CDN

将第三方的类库放到CDN上,能够 大幅度减少 生产环境中的项目体积,另外CDN能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上

通俗的来说就是提升项目中的静态文件的传输速度,在vue-cli3中可以通过externals配置项,将第三方的类库的引用地址从本地指向你提供的CDN地址

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

这里通过环境变量来判断生产环境才启用CDN,除了需要开启CDN外,你还需要在index.html注入CDN的域名,所以我这里通过html-webpack-plugin根据cdn域名动态的注入script标签,同时需要在index.html中通过模版的语法声明循环的数组和注入的元素

打包前的index.html:

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

打包后的index.html:

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

可以看到通过这个插件可以将cdn域名动态的注入到打包后的index.html中

还有一点要注意的是,externals对象的属性为你引入包的名字,而属性值是对应的AMD模块名字(这个名字比较特殊,一般常用的我已经列出来了,其余的第三方类库名字可以到访问对应的CDN在源码中寻找,一般在开头行都会有声明,导入有困难的还可以看下这篇博客 webpack externals 深入理解

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

gzip

为你的文件开启gzip压缩是一个不错的选择,通常开启gzip压缩能够有效的缩小传输资源的大小,如果你的项目是用nginx作为web服务器的话,只需在nginx的配置文件中配置相应的gzip选项就可以让你的静态资源服务器开启gzip压缩

#开启和关闭gzip模式
    gzip on;
    #gizp压缩起点,文件大于1k才进行压缩
    gzip_min_length 1k;
    # gzip 压缩级别,1-9,数字越大压缩的越好,也越占用CPU时间
    gzip_comp_level 6;
    # 进行压缩的文件类型。
    gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript ;
    #nginx对于静态文件的处理模块,开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
    gzip_static on
    # 是否在http header中添加Vary: Accept-Encoding,建议开启
    gzip_vary on;
    # 设置gzip压缩针对的HTTP协议版本
    gzip_http_version 1.1;
复制代码

但是我们这里要说的是前端输出gzip文件,利用compression-webpack-plugin让webpack在打包的时候输出.gz后缀的压缩文件

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化
我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

这样不需要服务器主动压缩我们就已经可以得到gzip文件,在上面的nginx配置项中可以发现这一行

#nginx对于静态文件的处理模块,开启后会寻找以.gz结尾的文件,直接返回,不会占用cpu进行压缩,如果找不到则不进行压缩
    gzip_static on
复制代码

只要把.gz的文件放到服务器上,开始gzip_static就可以让服务器优先返回.gz文件,在面对高流量时,也能一定程度减轻对服务器的压力,属于用空间来换时间(.gz文件会额外占有服务器的空间)

资源嗅探

对于 现代浏览器 来说,可以给link标签添加preload,prefetch,dns-prefetch属性

  • preload可以让浏览器尽早发现提前加载资源,而不是等到解析到当前标签才发http请求
  • prefetch可以让浏览器提前加载下个页面可能会需要的资源
  • dns-prefetch可以让浏览器提前对域名进行解析,减少DNS查找的开销

vue-cli3默认会给所有懒加载的路由添加prefetch属性,如果你的静态资源和后端接口不是同一个服务器的话,可以将你后端的域名放入link标签加入dns-prefetch属性

京东首页也使用到了dns-prefetch技术

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

http2

http2从2015年问世以来已经走过了4个年头,如今在国内也有超过50%的覆盖率,得益于http2的分帧传输,它能够极大的减少http(s)请求开销

http2和http1.1的性能差异对比

如果系统首屏同一时间需要加载的静态资源非常多,但是浏览器对同一域名的tcp连接数量是有限制的(chrome为6个)超过规定数量的tcp连接,则必须要等到之前的请求收到响应后才能继续发送,而http2则可以在一个tcp连接中并发多个请求没有限制,在一些网络较差的环境开启http2性能提升尤为明显

这里极力推荐在支持https协议的服务器中使用http2协议,可以通过web服务器Nginx配置,或是直接让服务器支持http2

静态资源优化

这部分旨在减少请求一些图片资源所造成的影响

图片懒加载

如果你的系统是一个偏展示的项目需要给用户展示大量图片,是否启用图片懒加载可能是你需要考虑的一个点,不在用户视野中的图片是没有必要加载的,图片懒加载通过让图片先加载成一张统一的图片,再给进入用户视野的图片替换真正的图片地址,可以同一时间减少http请求开销,避免显示图片导致的画面抖动,提高用户体验

下面我提供2种图片懒加载的思路,这2个方案最终都是用将占位的图片替换成真正的图片,然后给img标签设置一个自定义属性data-src存放真正的图片地址,src存放占位图片的地址

  1. 使用 getBoundingClientRect 该DOM节点相关的CSS边框集合,它返回一个对象,其中有一个top属性代表当前DOM节点距离浏览器窗口顶部的高度,判断是否小于当前浏览器窗口的高度(window.innerHeight),若小于说明已经进入用户视野,然后替换为真正的图片即可,同时需要监听scroll事件不停的执行上述操作(需要进行节流)
  2. 使用IntersectionObserver构造器传入一个回调函数,生成一个实例observer,这个实例有一个observe方法能够监听指定元素是否进入视图,进入则会触发之前的回调函数。同时给回调函数传入一个entries的参数,记录着这个实例观察的所有元素的一些阈值信息,其中intersectionRatio大于0表示进入了用户视野
我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

此时替换为真实的图片,并且调用实例的unobserve将这个img元素从这个实例的观察列表的去除

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

这2种的区别在于监听的方式,我个人更推荐使用Intersection Observer,因为监听scroll事件开销比较大,而让将这个工作交给另一个线程异步的去监听开销会小很多,但是它的缺点是一些老版本的浏览器可能支持率不高,好在社区有polyfill的方案

或者可以直接使用第三方的组件库vue-lazyload

使用svg图标

相对于用一张图片来表示图标,svg拥有更好的图片质量,体积更小,并且不需要开启额外的http请求,svg是一个未来的趋势,阿里的图标库iconfont支持导出svg格式的图标,但是在项目中需要封装一个支持svg的组件,具体封装的教程可以参考花裤衩的文章这里就不多赘述了 手摸手,带你优雅的使用 icon ,或者可以参考我的github

使用webp图片

webp图片最初在2010年发布,目标是减少文件大小,但达到和JPEG格式相同的图片质量,希望能够减少图片档在网络上的发送时间。webp图片无损比png图片无损的平均体积要小 20%~40%,并且图片质量用肉眼看几乎没什么差别

webp图片的缺点是兼容性并不是那么的好,在can l use 上查到webp图片的支持率并不是那么的理想。但是我们仍可以在支持webp图片的浏览器中使用它,而在不支持的浏览器提供png图片

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

编码相关

编码这方面主要是减少对DOM的访问,减少浏览器的重排/重绘,访问DOM是非常昂贵的操作,因为会涉及到2个不同的线程交互(JS线程和UI渲染线程)并且DOM本身又是一个非常笨重的对象,这里给出几个建议

  • 如果有需要动态创建DOM的需求,可以创建一个文档碎片(DocumentFragment),在文档碎片中操作因为不是在当前文档流不会引起重排/重绘,最后再一次性插入DOM节点

  • 避免频繁获取视图信息(getBoundingClientRect,clientWidth,offsetWidth),当发生重排/重绘操作时浏览器会维护一个队列,等到超过了最大值或过了指定时间(1000ms/60 = 16.6ms)才会去清空队列一次性执行操作,这样可以节省性能,而获取视图信息会立刻清空队列执行重排/重绘

  • 高频的监听事件使用函数防抖/节流(可以使用lodash库的throttle函数,但是推荐先搞懂原理)

  • 特效可以考虑单独触发渲染层(CSS3的transform会触发渲染层),动画可以使用绝对定位脱离文档流

开发过程中小技巧

使用require.context这个webpack的api可以避免每次引入一个文件都需要显式的用import导入,它可以扫描你指定的文件,然后全部导入到指定文件,可以用在

  • vue-router的路由自动导入
  • vuex的模块自动导入
  • svg图标的自动导入
  • 全局组件的自动导入

vuex:

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

全局组件和svg图标:

我是如何让公司后台管理系统焕然一新的(上) -项目重构,性能优化

有兴趣的朋友可以看看我另一篇介绍这个api的博客


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python学习手册

Python学习手册

Mark Lutz / 侯靖 / 机械工业出版社 / 2009-8 / 89.00元

《Python学习手册(第3版)》讲述了:Python可移植、功能强大、易于使用,是编写独立应用程序和脚本应用程序的理想选择。无论你是刚接触编程或者刚接触Python,通过学习《Python学习手册(第3版)》,你可以迅速高效地精通核心Python语言基础。读完《Python学习手册(第3版)》,你会对这门语言有足够的了解,从而可以在你所从事的任何应用领域中使用它。 《Python学习手册(......一起来看看 《Python学习手册》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具