页面的性能指标
- DCL(DOMContentLoaded),DOM解析完毕。
- FP(First Paint),表示渲染出第一个像素点。FP一般在HTML解析完成或者解析一部分时候触发。
- FCP(First Contentful Paint),表示渲染出第一个内容,这里的“内容”可以是文本、图片、canvas。
- FMP(First Meaningful Paint),首次渲染有意义的内容的时间,“有意义”没有一个标准的定义,FMP的计算方法也很复杂。
- LCP(largest contentful Paint),最大内容渲染时间。
白屏时间(FP) = 地址栏输入网址后回车 - 浏览器出现第一个元素
首屏时间(FCP) = 地址栏输入网址后回车 - 浏览器第一屏渲染完成
如何了解当前页面的性能状况
- 通过Chrome浏览器的Lighthouse和performance功能面板查看资源数据 【performance面板的操作流程】
- 引入Google Chrome官方的 web-vitals 包进行数据搜集
const reportWebVitals = onPerfEntry => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); getLCP(onPerfEntry); getTTFB(onPerfEntry); }); } }; export default reportWebVitals;
- 为了更准确的收集性能数据,可以通过performance API获取到响应的指标值。
白屏
一般是认为DOM Tree构建时,解析到的时候,我们认为是白屏结束的时间点。
我们可以在这个时候使用performace.mark进行打点标记,最后可以通过performance的
entry.startTime来获取白屏时间,其中entry.startTime是相对于performance.timing.navigationStart的时间。<head> ... <script> // 通常在head标签尾部时,打个标记,这个通常会视为白屏时间 performance.mark("first paint time"); </script> </head> <body> ... <script> // get the first paint time const fp = Math.ceil(performance.getEntriesByName('first paint time')[0].startTime); </script> </body>
首屏
一般是首屏中的图片加载完毕的时候,我们认为是首屏结束的时间点。我们可以对首屏中的image做onload事件绑定,
performace.mark进行打点标记,不过打点前先进行performance.clearMarks清除操作,以获取到多张图片最后加载完毕的时间。<body> <div class="app-container"> <img src="a.png" onload="heroImageLoaded()"> <img src="b.png" onload="heroImageLoaded()"> <img src="c.png" onload="heroImageLoaded()"> </div> <script> // 根据首屏中的核心元素确定首屏时间 performance.clearMarks("hero img displayed"); performance.mark("hero img displayed"); function heroImageLoaded() { performance.clearMarks("hero img displayed"); performance.mark("hero img displayed"); } </script> ... ... <script> // get the first screen loaded time const fmp = Math.ceil(performance.getEntriesByName('hero img displayed')[0].startTime); </script> </body>
此外,Google也提供了一些新的API——
PerformanceObserver
,来获取相应的指标值。首次绘制 (FP)/首次内容绘制 (FCP)
PerformanceObserver
为我们提供的新功能是,能够在性能事件发生时订阅这些事件,并以异步方式响应事件。let perfomanceMetrics = {}; const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { // `entry` is a PerformanceEntry instance. // `name` will be either 'first-paint' or 'first-contentful-paint'. const metricName = entry.name; const time = Math.round(entry.startTime + entry.duration); if (metricName === 'first-paint') { perfomanceMetrics.fp = time; } if (metricName === 'first-contentful-paint') { perfomanceMetrics.fcp = time; } } });
页面的性能优化
渲染层面
- 优化文件资源的异步加载及处理顺序,不要阻塞页面的渲染
- 通过服务端渲染同构直出
(路由同构,数据同构,渲染同构)
省去了客户端二次请求数据的网络传输开销,服务端的网络环境要优于客户端,内部服务器之间通信路径也更短。首屏加载时间(FCP)更快,同时也有更好的 seo。
缓存层面
- 离线数据可以缓存到localStorage
- webpack配置chunkhash/contenthash实现资源依赖长期缓存(强缓存/协商缓存)
chunkhash根据两个chunk自身的内容,通过某种算法来生成各自的hash,不会相互影响。
contenthash的意思是hash的生成,是基于自身内容来创建的。css chunk和app chunk的hash因为是基于各自内容的生成的,现在他们hash是不一样的。修改任何一方都不会影响另外一方。
请求层面
- 降低请求资源大小
(1)gzip可以有效压缩文件资源的体积
(2)雪碧图合并图片资源
(3)对于图片的处理,可以按支持情况使用webp/avif格式,且进行图片懒加载
(4)webpack打包时利用插件优化资源大小
url-loader(将小图片转成base64打包进 html 中,减小图片数量);
terser-webpack-plugin、uglifyjs-webpack-plugin(压缩 js 文件);
webpack SplitChunksPlugin (更合理的拆包策略)【查看更多】
- 加快请求速度
(1)升级HTTP2.0,利用多路复用让请求可以并行
(2)图片等静态资源上传到CDN服务【CDN加速原理】
(3)配置DNS预解析【DNS预解析详解】
①用meta信息来告知浏览器, 当前页面要做DNS预解析 <meta http-equiv="x-dns-prefetch-control" content="on">
②在页面header中使用link标签来强制对DNS预解析: <link rel="dns-prefetch" href="//www.zhix.net">
内嵌的webview网页优化
- 离线压缩包
服务端渲染直出html后仍然需要加载一个html文件,离线包基本思路通过通过webview统一拦截url, 将资源映射到本地离线包【事先打包好的文件资源】, 更新的时候对版本资源检测, 下载和维护本地缓存目录中的资源
- 预加载webView
- webView复用
打包构建的性能优化
- 使用 webpack-bundle-analyzer 查看打包出来文件的体积大小,看看是哪个包太大了。
- 在排除了包大小的问题之后,再使用 speed-measure-webpack-plugin 插件来查询是哪个插件或者是 loader 整慢了。
- 发现有些插件的运行确实慢了,但是我们也没精力去改人家的代码,所以还是牺牲自己电脑的内存吧,比较常见的方案就是使用 happypack / thread-loader来进行多进程构建。(原理类似,每次 webpack 解析一个模块,happypack / thread- loader 会将它及它的依赖分配给 worker 线程中,从而达到多进程打包的目的。)
- 生产环境开启 tree shaking 去掉没有引用的代码。
- 引入 dllplugin+dllReferencePlugin 动态链接库方案,将第三方库单独打包,再链入我们的webpack项目中。(对于现代化前端,我们总是会应用各种库,这些库我们并不会频繁去变动他们,为了避免每次都是构建这些不常变动的代码,可以把这些代码抽离出来,webpack中,我们可以结合DllPlugin 和 DllReferencePlugin插件来实现,在实操中还是用 polyfill 来进行引入对应库的静态资源,连打包都省掉了,只用使用 externals 来引入这些我们埋在 html 的 js 资源。)
- 其实在构建过程主要是 js 的编译这块可能会占用很多的时间来构建,所以 babel-loader 的 cache 属性来配置缓存或者直接使用 cache-loader 来进行缓存来提升二次构建速度。
- 通过 rule 的 include 属性来缩小构建目标,resolve 的具体配置来减少文件搜索范围。