提升首屏的加载速率,是前端性能优化中最告急的环节,这里笔者梳理出一些 通例且有效 的首屏优化发起
目标:通过对比优化前后的性能变革,来验证方案的有效性,相识并把握其原理
1、路由懒加载
SPA 项目,一个路由对应一个页面,如果不做处理惩罚,项目打包后,会把全部页面打包成一个文件,当用户打开首页时,会一次性加载全部的资源,造成首页加载很慢,低沉用户体验。
列一个实际项目标打包详情:
将路由全部改成懒加载
// 通过webpackChunkName设置分割后代码块的名字const Home = () => import(/* webpackChunkName: "home" */ "@/views/home/index.vue");const MetricGroup = () => import(/* webpackChunkName: "metricGroup" */ "@/views/metricGroup/index.vue");…………const routes = [ { path: "/", name: "home", component: Home }, { path: "/metricGroup", name: "metricGroup", component: MetricGroup }, ………… ]重新打包后,首页资源拆分为 app.js 和 home.js,以及对应的 css 文件
通过路由懒加载,该项目标首页资源压缩约 52%
路由懒加载的原理
懒加载条件的实现:ES6的动态地加载模块——import()
调用 import() 之处,被作为分离的模块出发点,意思是,被哀求的模块和它引用的全部子模块,会分离到一个单独的 chunk 中
——摘自《webpack——模块方法》的import()末节
要实现懒加载,就得先将举行懒加载的子模块分离出来,打包成一个单独的文件。
webpackChunkName 作用是 webpack 在打包的时间,对异步引入的库代码(lodash)举行代码分割时,设置代码块的名字。webpack 会将任何一个异步模块与类似的块名称组合到类似的异步块中。
2、组件懒加载
除了路由的懒加载外,组件的懒加载在许多场景下也有告急的作用
举个?:
home 页面 和 about 页面,都引入了 dialogInfo 弹框组件,该弹框不是一进入页面就加载,而是须要用户手动触发后才展示出来
home 页面示例:
<template> <div终极,使用组件路由懒后,该项目标首页资源进一步镌汰约 11%
组件懒加载的使用场景
偶然资源拆分的过细也欠好,大概会造成欣赏器 http 哀求的增多
总结出三种得当组件懒加载的场景:
1)该页面的 JS 文件体积大,导致页面打开慢,可以通过组件懒加载举行资源拆分,使用欣赏器并行下载资源,提升下载速率(比如首页)
2)该组件不是一进入页面就展示,须要肯定条件下才触发(比如弹框组件)
3)该组件复用性高,许多页面都有引入,使用组件懒加载抽离出该组件,一方面可以很好使用缓存,同时也可以镌汰页面的 JS 文件大小(比如表格组件、图形组件等)
3、公道使用 Tree shaking
Tree shaking 的作用:消除无用的 JS 代码,镌汰代码体积
举个?:
// util.jsexport function targetType(target) { return Object.prototype.toString.call(target).slice(8, -1).toLowerCase();}export function deepClone(target) { return JSON.parse(JSON.stringify(target));}项目中只使用了 targetType 方法,但未使用 deepClone 方法,项目打包后,deepClone 方法不会被打包到项目里
tree-shaking 原理:
依靠于ES6的模块特性,ES6模块依靠关系是确定的,和运行时的状态无关,可以举行可靠的静态分析,这就是 tree-shaking 的底子
静态分析就是不须要实行代码,就可以从字面量上对代码举行分析。ES6之前的模块化,比如 CommonJS 是动态加载,只有实行后才知道引用的什么模块,就不能通过静态分析去做优化,正是基于这个底子上,才使得 tree-shaking 成为大概
Tree shaking 并不是万能的
并不是说全部无用的代码都可以被消除,照旧上面的代码,换个写法 tree-shaking 就失效了
// util.jsexport default { targetType(target) { return Object.prototype.toString.call(target).slice(8, -1).toLowerCase(); }, deepClone(target) { return JSON.parse(JSON.stringify(target)); }};// 引入并使用import util from '../util';util.targetType(null)同样的,项目中只使用了 targetType 方法,未使用 deepClone 方法,项目打包后,deepClone 方法照旧被打包到项目里
在 dist 文件中搜刮 deepClone 方法
究其缘故原由,export default 导出的是一个对象,无法通过静态分析判断出一个对象的哪些变量未被使用,以是 tree-shaking 只对使用 export 导出的变量见效
这也是函数式编程越来越火的缘故原由,由于可以很好使用 tree-shaking 精简项目标体积,也是 vue3 全面拥抱了函数式编程的缘故原由之一
4、骨架屏优化白屏时长
使用骨架屏,可以紧缩白屏时间,提升用户体验。
国内大多数的主流网站都使用了骨架屏,特殊是手机端的项目。
SPA 单页应用,无论 vue 照旧 react,最初的 html 都是空缺的,须要通过加载 JS 将内容挂载到根节点上,这套机制的副作用:会造成长时间的白屏。
常见的骨架屏插件就是基于这种原理,在项目打包时将骨架屏的内容直接放到 html 文件的根节点中。
使用骨架屏插件,打包后的 html 文件(根节点内部为骨架屏):
同一项目,对比使用骨架屏前后的 FP 白屏时间:
- 无骨架屏:白屏时间 1063ms
- 有骨架屏:白屏时间 144ms
骨架屏确实是优化白屏的不二选择,白屏时间紧缩了 86%
骨架屏插件
这里以 vue-skeleton-webpack-plugin 插件为例,该插件的亮点是可以给差别的页面设置差别的骨架屏,这点确实很酷
1)安装
npm i vue-skeleton-webpack-plugin
2)vue.config.js 设置
// 骨架屏const SkeletonWebpackPlugin = require("vue-skeleton-webpack-plugin");module.exports = { configureWebpack: { plugins: [ new SkeletonWebpackPlugin({ // 实例化插件对象 webpackConfig: { entry: { app: path.join(__dirname, './src/skeleton.js') // 引入骨架屏入口文件 } }, minimize: true, // SPA 下是否须要压缩注入 HTML 的 JS 代码 quiet: true, // 在服务端渲染时是否须要输出信息到控制台 router: { mode: 'hash', // 路由模式 routes: [ // 差别页面可以设置差别骨架屏 // 对应路径所须要的骨架屏组件id,id的界说在入口文件内 { path: /^\/home(?:\/)?/i, skeletonId: 'homeSkeleton' }, { path: /^\/detail(?:\/)?/i, skeletonId: 'detailSkeleton' } ] } }) ] }}3)新建 skeleton.js 入口文件
// skeleton.jsimport Vue from "vue";// 引入对应的骨架屏页面import homeSkeleton from "./views/homeSkeleton";import detailSkeleton from "./views/detailSkeleton";export default new Vue({ components: { homeSkeleton, detailSkeleton, }, template: ` <div> <homeSkeleton id="homeSkeleton" style="display:none;" /> <detailSkeleton id="detailSkeleton" style="display:none;" /> </div> `,});5、长列表假造滚动
首页中不乏有须要渲染长列表的场景,当渲染条数过多时,所须要的渲染时间会很长,滚动时还会造成页面卡顿,团体体验非常欠好
假造滚动——指的是只渲染可视地区的列表项,非可见地区的不渲染,在滚动时动态更新可视地区,该方案在优化大量数据渲染时效果是很显着的
假造滚动图例:
假造滚动根本原理:
盘算出 totalHeight 列表总高度,并在触发时滚动变乱时根据 scrollTop 值不停更新 startIndex 以及 endIndex ,以此从列表数据 listData 中截取对应元素
假造滚动性能对比:
使用假造滚动使性能提升了 78%
假造滚动插件
假造滚动的插件有许多,比如 vue-virtual-scroller、vue-virtual-scroll-list、react-tiny-virtual-list、react-virtualized 等
这里简单先容 vue-virtual-scroller 的使用
// 安装插件npm install vue-virtual-scroller// main.jsimport VueVirtualScroller from 'vue-virtual-scroller'import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'Vue.use(VueVirtualScroller)// 使用<template> <RecycleScroller 测试实行:
如果直接把下面这段代码直接丢到主线程中,盘算过程中页面不停处于卡死状态,无法操纵
let sum = 0;for (let i = 0; i < 200000; i++) { for (let i = 0; i < 10000; i++) { sum += Math.random() } }使用 Web Worker 实行上述代码时,盘算过程中页面正常可操纵、无卡顿
// worker.jsonmessage = function (e) { // onmessage获取传入的初始值 let sum = e.data; for (let i = 0; i < 200000; i++) { for (let i = 0; i < 10000; i++) { sum += Math.random() } } // 将盘算的效果通报出去 postMessage(sum);}Web Worker 详细的使用与案例,详情见 一文彻底相识Web Worker,十万、百万条数据都是弟弟?
Web Worker 的通讯时长
并不是实行时间高出 50ms 的使命,就可以使用 Web Worker,还要先考虑通讯时长的题目
如果一个运算实行时长为 100ms,但是通讯时长为 300ms, 用了 Web Worker大概会更慢
比如新建一个 web worker, 欣赏器会加载对应的 worker.js 资源,下图中的 Time 是这个资源的通讯时长(也叫加载时长)
当使命的运算时长 - 通讯时长 > 50ms,保举使用Web Worker
7、requestAnimationFrame 制作动画
requestAnimationFrame 是欣赏器专门为动画提供的 API,它的革新频率与表现器的频率保持同等,使用该 api 可以办理用 setTimeout/setInterval 制作动画卡顿的环境
下面的案例演示了两者制作进度条的对比(运行按钮可点击)
可以看到使用定时器制作的动画,卡顿照旧比力显着的
jcode
setTimeout/setInterval、requestAnimationFrame 三者的区别:
1)引擎层面
setTimeout/setInterval 属于 JS引擎,requestAnimationFrame 属于 GUI引擎
JS引擎与GUI引擎是互斥的,也就是说 GUI 引擎在渲染时会壅闭 JS 引擎的盘算
2)时间是否正确
requestAnimationFrame 革新频率是固定且正确的,但 setTimeout/setInterval 是宏使命,根据变乱轮询机制,其他使命会壅闭或耽误js使命的实行,会出现定时器禁绝的环境
3)性能层面
当页面被潜伏或最小化时,setTimeout/setInterval 定时器仍会在背景实举措画使命,而使用 requestAnimationFrame 当页面处于未激活的状态下,屏幕革新使命会被体系停息
8、JS 的6种加载方式
1)正常模式
<script src="index.js"></script>
这种环境下 JS 会壅闭 dom 渲染,欣赏器必须等候 index.js 加载和实行完成后才华去做别的事变
2)async 模式
<script async src="index.js"></script>
async 模式下,它的加载是异步的,JS 不会壅闭 DOM 的渲染,async 加载是无序次的,当它加载竣事,JS 会立即实行
使用场景:若该 JS 资源与 DOM 元素没有依靠关系,也不会产生其他资源所须要的数据时,可以使用async 模式,比如埋点统计
3)defer 模式
<script defer src="index.js"></script>
defer 模式下,JS 的加载也是异步的,defer 资源会在 DOMContentLoaded 实行之前,而且 defer 是有序次的加载
如果有多个设置了 defer 的 script 标签存在,则会按照引入的前后序次实行,即便是反面的 script 资源先返回
以是 defer 可以用来控制 JS 文件的实行序次,比如 element-ui.js 和 vue.js,由于 element-ui.js 依靠于 vue,以是必须先引入 vue.js,再引入 element-ui.js
<script defer src="vue.js"></script><script defer src="element-ui.js"></script>defer 使用场景:一样平常环境下都可以使用 defer,特殊是须要控制资源加载序次时
4)module 模式
<script type="module">import { a } from './a.js'</script>
在主流的当代欣赏器中,script 标签的属性可以加上 type="module",欣赏器会对其内部的 import 引用发起 HTTP 哀求,获取模块内容。这时 script 的运动会像是 defer 一样,在背景下载,而且等候 DOM 剖析
Vite 就是使用欣赏器支持原生的 es module 模块,开辟时跳过打包的过程,提升编译服从
5) preload
<link rel="preload" as="script" href="index.js">
link 标签的 preload 属性:用于提前加载一些须要的依靠,这些资源会优先加载(如下图红框)
vue2 项目打包天生的 index.html 文件,会自动给首页所须要的资源,全部添加 preload,实现关键资源的提前加载
preload 特点:
1)preload 加载的资源是在欣赏器渲染机制之进步行处理惩罚的,而且不会壅闭 onload 变乱;
2)preload 加载的 JS 脚本其加载和实行的过程是分离的,即 preload 会预加载相应的脚本代码,待到须要时自行调用;
6)prefetch
<link rel="prefetch" as="script" href="index.js">
prefetch 是使用欣赏器的空闲时间,加载页面未来大概用到的资源的一种机制;通常可以用于加载其他页面(非首页)所须要的资源,以便加速后续页面的打开速率
prefetch 特点:
1)pretch 加载的资源可以获取非当前页面所须要的资源,而且将其放入缓存至少5分钟(无论资源是否可以缓存)
2)当页面跳转时,未完成的 prefetch 哀求不会被停止
加载方式总结
async、defer 是 script 标签的专属属性,对于网页中的其他资源,可以通过 link 的 preload、prefetch 属性来预加载
现在当代框架已经将 preload、prefetch 添加到打包流程中了,通过机动的设置,去使用这些预加载功能,同时我们也可以审时度势地向 script 标签添加 async、defer 属性行止理惩罚资源,这样可以明显提升性能
9、图片的优化
平常大部分性能优化工作都会合在 JS 方面,但图片也是页面上非常告急的部分
特殊是对于移动端来说,完全没有须要去加载原图,浪费带宽。怎样去压缩图片,让图片更快的展示出来,有许多优化工作可以做
淘宝首页的图片资源都很小:
图片的动态裁剪
许多云服务,比如阿里云或七牛云,都提供了图片的动态裁剪功能,效果很棒,确实是钱没有白花
只需在图片的url地点上动态添加参数,就可以得到你所须要的尺寸大小,比如:http://7xkv1q.com1.z0.glb.clouddn.com/grape.jpg?imageView2/1/w/200/h/200
图片瘦身前后对比:
颠末动态裁剪后的图片,加载速率会有非常显着的提升
图片的懒加载
对于一些图片量比力大的首页,用户打开页面后,只须要出现出在屏幕可视地区内的图片,当用户滑动页面时,再去加载出现在屏幕内的图片,以优化图片的加载效果
图片懒加载实现原理:
由于欣赏器会自动对页面中的 img 标签的 src 属性发送哀求并下载图片,可以通过 html5 自界说属性 data-xxx 先暂存 src 的值,然后在图片出现在屏幕可视地区的时间,再将 data-xxx 的值重新赋值到 img 的 src 属性即可
<img src="" alt="" data-src="./images/1.jpg"><img src="" alt="" data-src="./images/2.jpg">这里以 vue-lazyload 插件为例
// 安装 npm install vue-lazyload // main.js 注册import VueLazyload from 'vue-lazyload'Vue.use(VueLazyload)// 设置项Vue.use(VueLazyload, { preLoad: 1.3, error: 'dist/error.png', // 图片加载失败时的占位图 loading: 'dist/loading.gif', // 图片加载中时的占位图 attempt: 1})// 通过 v-lazy 指令使用<ul> <li v-for="img in list"> <img v-lazy="img.src" :key="img.src" > </li></ul>使用字体图标
字体图标是页面使用小图标的不二选择,最常用的就是 iconfont
字体图标的长处:
1)轻量级:一个图标字体要比一系列的图像要小。一旦字体加载了,图标就会立刻渲染出来,镌汰了 http 哀求
2)机动性:可以随意的改变颜色、产生阴影、透明效果、旋转等
3)兼容性:险些支持全部的欣赏器,请放心使用
图片转 base64 格式
将小图片转换为 base64 编码字符串,并写入 HTML 大概 CSS 中,镌汰 http 哀求
转 base64 格式的优缺点:
1)它处理惩罚的通常黑白常小的图片,由于 Base64 编码后,图片大小会膨胀为原文件的 4/3,如果对大图也使用 Base64 编码,后者的体积会显着增加,即便镌汰了 http 哀求,也无法补充这巨大的体积带来的性能开销,得不偿失
2)在传输非常小的图片的时间,Base64 带来的文件体积膨胀、以及欣赏器剖析 Base64 的时间开销,与它节省掉的 http 哀求开销相比,可以忽略不计,这时间才华真正表现出它在性能方面的优势
项目可以使用 url-loader 将图片转 base64:
// 安装npm install url-loader --save-dev // 设置module.exports = { module: { rules: [{ test: /.(png|jpg|gif)$/i, use: [{ loader: 'url-loader', options: { // 小于 10kb 的图片转化为 base64 limit: 1024 * 10 } }] }] }};
|