Skip to content

Latest commit

 

History

History
163 lines (100 loc) · 10.1 KB

性能方面的探索.md

File metadata and controls

163 lines (100 loc) · 10.1 KB

最近在做一些基础的优化,总结一下思考的点:

项目主要面向Vue:

Vue代码层面的性能优化:

  1. 首屏 main.js 的内容挂载优化,插件库尽量按需加载。

  2. 无用/不必首屏的挂载尽量放在后面。

  3. router处的懒加载。

  4. 组件内的异步组件和router其实是一个思路。

  5. 减少接口请求,比如列表内删除,新增,这些尽可能不通过重新请求或者尽可能少的重新请求。

  6. ws下内容过多导致的dom特别多的优化 -> 参考无限滚动的列表,只展示视觉范围内的dom,或者按斗鱼这些直播平台的弹幕列表里的只显示固定数量的dom。

  7. 缓存,代码层面的缓存可以有keep-alive,也可以有特定接口的缓存,如keep-alive相对于一个组件而言,但一个组件内可能会请求很多接口,有些接口没有那么大的变动频率,比如已登录用户的信息,可以自写一个缓存系统(LRU等)。

  8. 重绘回流下的优化,这些还没有遇到过。 遇到一个object-fit: cover的性能问题:object-fit;cover会使得图片适应当前区域,如果这个图片的原像素与当前区域差别过大,那么在进行滚动后的重绘时会极大的消耗性能,造成FPS的下降。 translateZ(0)创建图层进行图层上的绘制然后交由浏览器合成。

    补一个回流的消除,lighthouse里有一项Cumulative Layout Shift,这个直译有点疑惑,累积的布局变换,这里面的统计包含每一次可视页面的变动,而回流一般会让所有元素进行重新渲染,所以这里面的layout shift包含了回流。这次实践的例子是一个内部子元素全都是fixed的组件但并没有让最外层的父元素也脱离文档流造成了没有用的布局消耗。 这里可选用的脱离有:absolute, fixed, 还有translateZ,float。需要特别注意的是,absolute和fixed会形成一个单独的布局空间(好像叫层叠上下文?)对内外部的z-index都有影响,而translateZ会将自身z-index清除(同时也会形成一个层叠上下文)。所里这里选用了float。

    https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context

  9. 大数据的优化,前端业务端对于算法的需求相对较小,在普通业务方面性能并不是一个需要关注的点,可能需要注意的大数据点是canvas绘制方面的动画window.requestAnimateFrame,按帧率刷新。

HTTP方面的性能优化:

  1. 首当其冲的是缓存和CDN, 缓存这里说一下一般通过HTTP头中的Cache-Control去定义: no-store 是没有缓存,完完全全每次都去服务器端拉数据
    no-cache 比no-store稍弱,这个策略会去服务器端寻求验证,如果资源未过期那么就用缓存否则拉取最新的。
    max-age max-age设置的是自请求发起时的秒数,在这个时间段内都会走缓存,相当于一种强缓存,在过期时间之前都不会向服务器发送任何请求 注意这里的不会 向服务器发送任何请求。
    must-revalidate 这个策略和no-cache非常相似,与max-age配合使用,如果超过了max-age那么会去验证缓存文件的新鲜度。
    last-modified或者etag去进行验证是否命中缓存,last-modified表示本地缓存文件的最后修改时间,如果和服务器端的同文件对起来那就使用缓存(如果本地打开过缓存文件但没有修改任何内容并保存那就会干掉缓存)etag则不根据文件的最后修改时间去算,至于根据什么算那需要去查一查,一般用即可,深究下去关键字:ETag算法,唯一,尽可能快速。

  2. HTTP/2多路复用。

  3. GZIP。

Webpack打包方面的优化:

  1. Tree-shaking。
  2. 分块。
  3. 压缩混淆。
  4. 静态GZIP。

基于Webpack的打包这里展开一下:

  1. 关于Tree-shaking,这个概念很简单,就是把无用的代码在打包的时候剔除。

在用webpack做打包的时候会按照import去做Tree-shaking分析,用require导入的包不在分析之列,同时还支持在package.json中写sideEffect指定哪些文件无副作用放心剔除。

关于副作用可能不好理解:

在之前的当前技能体系.md中写到的一个副作用的例子:

class Scroll {
    handleEvent() {
        console.log('You called me.')
    }
}

window.addEventListener('visibilitychange', new Scroll())

全程不存在handleEvent的调用,但它就不能被Tree-shaking掉,因为如果添加的事件回调是个类调用的是handleEvent方法。

类似的还有在Tree-shaking lodash时,一般会

import cloneDeep from 'lodash/cloneDeep'

精准导入,或者用它import封装的

import { cloneDeep } from 'lodash-es'

这两者都可以将lodash进行Tree-shaking,只导入cloneDeep而不导入整个包,但lodash内有一个具有副作用的方法————chain。

这个方法是将所有其他方法串联起来使用的,而它是在index里内通过prototype挂载的(不同于上面例子的另一种副作用形式,基于动态语言的特性动态的修改prototype)。

所以直接import chain from 'lodash/chain'可以导入但是用不了,想用就只能全部引入lodash或者另辟蹊径的手动挂载prototype。

在Vue中常见的副作用是各种ref的跨组件访问,在当前组件中没有用到这个method,在父组件或者兄弟组件内通过ref访问到了这个method,尤其是当ref还是动态设置的时候,基本无法静态分析出这个method会不会被调用到。

总结:

尽量写无副作用的代码,引用第三方包的时候尽量按最小的块引用,能用import的就用import,保证Tree-shaking机制正常运行。

  1. 关于分块,分块或许是个容易一开始注意到但越往后写越来越容易忽略的点。

在写Vue项目时入门就用了Vue-Router文档内提到的import()动态函数来对各种不同路由进行懒加载分块,除此之外的分块很少被提及,但这一部分同样重要。

在具体的组件内同样可以用到分块,比如在一个tabs中,对于非首页的tab,完全可以也通过import()来进行分块来提升各方面的性能,所需要的代价只有网络传输一个,这个代价是可以接受的,如果用户本身网络不好,一次性加载过多的内容反而体验更加糟糕。

在具体的method内,如果是一个极端条件下触发的操作,比如加解密RSA,如果一开始就导入整个加解密的包,那这一个包的传输完全是浪费带宽,也可以用import()取代import来达到分块加载的目的。

在进行具体的分块时有一个需要注意的点:

如果同一个模块:

a.js

import('c.js')

b.js

import c from 'c.js'

那么a在运行到import('c.js')会去b.js里加载,这不是一个期望的操作,期望的操作应该是单独加载c.js或者一个可以预见的加载集合。

这里在分块时要注意一下,引入b.js可能同时也会加载b.css。

  1. 压缩混淆

用插件。

  1. 静态GZIP

用插件。

其他

在页面到达之前,Nginx(或其他的Apache等)做负载均衡,

数据库方面了解不多,关键字分库,分表,读写分离,主从,缓存。

最近在项目调优上的一些进展。

在核心To C产品上的优化,目的是提高用户打开目标页面的速度,技术栈是Nuxt,一开始的lighthouse给出的速率是10s左右,performance里从白屏到首次有画面出现的节点在1800-2000ms之间(从lighthoust给的内容track),单独performance的话一开始的时间已经忘记了没有记录。

目前一共做了两次优化,一次是针对服务器端渲染的配合下的优化,另一次则是一个比较通用的优化。

先说第一次,产品有一个核心页面,未优化前所有此页面内需要的组件会一次性加载出来。

这样其实并不合理,和动态路由一样,页面内也可以再做一层组件优化:

  1. ssr渲染下动态组件配合client-only(nuxt),可以做成优先加载某些组件延迟加载某些组件。比如核心功能是播放器,那就优先加载播放器,其他的内容全都是preload的客户端预加载,当内容返回到客户端,播放器已经有了,其他的内容慢慢加载出来。

  2. 用if判断的内容也可以用动态组件。

  3. preload预加载资源css/js,这里注意,css资源如果预加载,chrome下不要设置为crossorigin,chrome会设置crossorigin的验证模式为omit,如果与后端不符的话会导致加载两次。

  4. 预加载DNS,预连接一些必连的内容。

  5. defer,对于可以非阻塞加载的js可以用defer让他们变为非阻塞状态。

  6. 缓存,这里着重说一下缓存,之前对缓存做了一个简单的概括,这次是一个很好的实践,源自于阿里的OSS缓存未生效。 缓存上面说到由cache-control控制,这里注意的是如果没有cache-control并非没有缓存,第二次请求时会走协商缓存的路数,304。

    强缓存上面,一般CDN都会设置一个过期时间 cache-control: max-age=600,然后由age来判断文件的新鲜程度(或者其他last-modified,eTag),不新鲜之后会走协商的路数。

    强缓存是最快的,从内存或者硬盘里拉取,几乎0ms,之后是协商缓存,协商缓存稍微快一点。

做了上面的内容之后,加载速度Up,现在从回车地址栏到有页面出来只需要700ms左右(在测试服务器下的测试,记录此日志时线上的环境经过一次优化已经降到了900ms,不过lighthouse里FCP给的是7s左右,这次在测试环境下只有2s),从完全的白屏到有loading大概有200ms,loading到有意义的页面出来大概是500ms。

  1. 老生常谈的分块,这里专门做的一个优化还有将CSS分块,我们有很多主题,之前将所有的主题CSS一次性加载,但这并不必须却又不好分块,目前还未做,做了之后在记录。

增加一个流程图:web程序优化思路。