1 路由懒加载
SPA单页面应用项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有资源,造成首页加载很慢,降低用户体验。(前后大概降低30%)
1
2
3
4{
5 name: 'personHomeHome',
6 path: '/personHome/home',
7 meta: {
8 title: '个人首页',
9 breadcrumb: false,
10 dot: false,
11 icon: 'iconfont iconzhuye1',
12 },
13 component: (resolve) => require(['@/views/personHome/home/home.vue'], resolve),
14},
15
16{
17 path: 'editor',
18 meta: {
19 requireLogin: true,
20 istrun:true
21 },
22 component: () => import( '@/views/form/editor')
23},
2 组件懒加载
除了路由的懒加载外,组件的懒加载在很多场景下也有重要的作用
例如:当用户打开某个页面,会一次性加载该页面所有的资源,我们期望的是有些组件使用户触发按钮后,再加载该弹窗组件的资源,例如弹窗组件,这时候,就可以考虑用懒加载的方式引入
1
2import treeTable from "@/components/table/treeTable.vue";
3import editDialog from "./editDialog.vue";
4export default {
5 components: {
6 treeTable,
7 editDialog,
8 },
9}
10
11const editDialog = ()=> import( "./editDialog.vue")
12const treeTable = ()=> import( "@/components/table/treeTable.vue");
13export default {
14 components: {
15 treeTable,
16 editDialog,
17 },
18}
此时打包之后,弹窗组件就被单独打包出来editDialog对应的js 和css ,当用户点击按钮时,才会去加载。
组件懒加载的场景:
- 该页面的js文件体积大,导致页面打开很慢,可以通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度。
- 该组件不是已进入页面就展示,需要一定条件下才触发(例如弹窗组件)
- 该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 js 文件大小(比如表格组件、图形组件)
3 优化分包策略
vue-cli3 的默认优化是将所有npm依赖都打进 chunk-vendor,但这种做法在依赖多的情况下导致 chunk-vendor 过大
splitChunks 优化分包
1 configureWebpack: (config) => {
2 if (process.env.NODE_ENV === 'production') {
3 config.optimization.splitChunks({
4 chunks: 'all',
5 cacheGroups: {
6 libs: {
7 name: 'chunk-libs',
8 test: /[\\/]node_modules[\\/]/,
9 priority: 10,
10 chunks: 'initial'
11 },
12 elementUI: {
13 name: 'chunk-elementUI',
14 priority: 20,
15 test: /[\\/]node_modules[\\/]_?element-ui(.*)/
16 },
17 commons: {
18 name: 'chunk-commons',
19 test: resolve('src/components'),
20 minChunks: 3,
21 priority: 5,
22 reuseExistingChunk: true
23 }
24 }
25 })
26 }
27
28 config.optimization.runtimeChunk('single')
29 }
externals 配合CDN 加速
用 webpack 的externals 属性把把不需要打包的库文件分离出去,减少打包后的文件的大小,用cdn 资源的方式将第三方依赖包引入项目,可以大大减少项目打包体积
1
2vue@2.7.14
3
4
5key: 就是 import AAA from 'BBB'中的 BBB
6value: 就是可以在引入CDN 资源后,在控制台打印 window.XXX, value 就是 XXX
7
8@1 CDN 引入vue
9如果npm 已安装了vue ,可以选择卸载,不卸载也行,但是 vue-template-compiler 需要安装,注意版本号和vue 版本保持一致
10yarn add vue-template-compiler@2.7.14 -S
11
12public文件夹里的index.html 文件引入vue
13<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
14@2 webpack.config.js中的配置
15
16chainWebpack (config) {
17 config.externals = {
18 vue: 'Vue',
19 },
20}
21
22原理是通过 cdn 引入的vue 会挂载到 window 下面, window.Vue
4 合理使用 Tree shaking
Tree shaking的作用:消除无用的js 代码,减少代码体积
例如:
1export function isFunction(obj) {
2 return typeof obj === "function" &&
3 typeof obj.nodeType !== "number" &&
4 typeof obj.item !== "function";
5 };
6export function isPlainObject(obj) {
7 let proto, Ctor;
8 if (!obj || toString.call(obj) !== "[object Object]") return false;
9 proto = Object.getPrototypeOf(obj);
10 if (!proto) return true;
11 Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
12 return typeof Ctor === "function" && Ctor === Object;
13 };
项目中只使用 isFunction 没有使用 isPlainObject 方法,项目打包后,isPlainObject 方法不会被打包到项目里
Tree shaking原理:
依赖于 ES6的模块特性, ES6 模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析这是 Tree shaking的基础
静态分析就是不需要执行代码,就可以从字面量上对代码进行分析。 ES6 之前的模块,比如 CommonJs 是动态加载,只有执行后才可以引用的什么模块,就不能通过静态分析去做优化,正是基于这个基础上,才使得 Tree shaking 成为可能
Tree shaking并不是万能的
并不是所有无用的代码都可以被消除,还是上面的代码,换个写法 Tree shaking 就失效了
1export default {
2 isFunction(obj) {
3 return typeof obj === "function" &&
4 typeof obj.nodeType !== "number" &&
5 typeof obj.item !== "function";
6 },
7 isPlainObject(obj) {
8 let proto, Ctor;
9 if (!obj || toString.call(obj) !== "[object Object]") return false;
10 proto = Object.getPrototypeOf(obj);
11 if (!proto) return true;
12 Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
13 return typeof Ctor === "function" && Ctor === Object;
14 },
15 }
同样的,项目中只使用 isFunction 没有使用 isPlainObject 方法,项目打包后,isPlainObject 方法还是被打包到项目里,
原因是 export default 导出的是一个对象,无法通过静态分析判断出一个对象的哪些变量未被使用,所以 tree shaking 只对使用 export 单个导出的变量生肖
这也是函数式编程越来越被青睐的原因,因为可以很好利用 tree-shaking 精简项目的体积
5 骨架屏优化白屏时长
SPA单页面应用,无论是 vue 还是 react ,最初的 html 都是空白的,需要通过加载 js 将内容挂载到跟节点上,这套机制的副作用:会造成长时间的白屏
常见的骨架屏插件就是基于这种原理,在项目打包时,将骨架屏的内容直接放到 html 文件的根节点中
骨架屏插件 vue-skeleton-webpack-plugin 插件为例,该插件的亮点时可以给不同的页面设置不同的骨架屏
1
2yarn add vue-skeleton-webpack-plugin
3
4
5
6const SkeletonWebpackPlugin = require("vue-skeleton-webpack-plugin");
7module.exports = {
8 configureWebpack: {
9 plugins: [
10 new SkeletonWebpackPlugin({
11
12 webpackConfig: {
13 entry: {
14 app: path.join(__dirname, './src/skeleton.js')
15 }
16 },
17 minimize: true,
18 quiet: true,
19 router: {
20 mode: 'hash',
21 routes: [
22
23
24 { path: /^\/home(?:\/)?/i, skeletonId: 'homeSkeleton' },
25 { path: /^\/detail(?:\/)?/i, skeletonId: 'detailSkeleton' }
26 ]
27 }
28 })
29 ]
30 }
31}
32
33
34import Vue from "vue";
35
36import homeSkeleton from "./views/homeSkeleton";
37import detailSkeleton from "./views/detailSkeleton";
38
39export default new Vue({
40 components: {
41 homeSkeleton,
42 detailSkeleton,
43 },
44 template: `
45 <div>
46 <homeSkeleton id="homeSkeleton" style="display:none;" />
47 <detailSkeleton id="detailSkeleton" style="display:none;" />
48 </div>
49 `,
50});
6 长列表虚拟滚动
首页中不乏有需要渲染长列表的场景,当渲染条数过多时,所需要的渲染时间会很长,滚动时还会造成页面卡顿,整体体验非常不好
虚拟滚动——指的是只渲染可视区域的列表项,非可见区域的不渲染,在滚动时动态更新可视区域,该方案在优化大量数据渲染时效果是很明显的
虚拟滚动原理: 计算出 totalHeight 列表总高度,并在触发滚动事件时根据 scrollTop 指不断更新 startIndex 以及 endIndex ,以此从列表数据 listData 中截取对应元素
虚拟滚动插件vue 和react 中都有很多 vue-virtual-scroller、vue-virtual-scroll-list、react 中有 react-tiny-virtual-list、react-virtualized
这里简单介绍 vue-virtual-scroller 的使用
1
2yarn add vue-virtual-scroller
3
4
5import VueVirtualScroller from 'vue-virtual-scroller'
6import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
7
8
9Vue.use(VueVirtualScroller)
10
11
12import Vue from 'vue'
13import { RecycleScroller } from 'vue-virtual-scroller'
14Vue.component('RecycleScroller', RecycleScroller)
15
16
17<template>
18 <RecycleScroller
19 class="scroller"
20 :items="list"
21 :item-size="32"
22 key-field="id"
23 v-slot="{ item }">
24 <div class="user"> {{ item.name }} </div>
25 </RecycleScroller>
26</template>
27
该插件主要有 RecycleScroller 和 DynamicScroller 这两个组件,其中 RecycleScroller 需要设置 item 高度,也就是列表每项 item 的高度都是一致的, 而 DynamicScroller 可以兼容 item 的高度为动态的
7 Web Worker 优化长任务
长任务: 执行时间超过 50ms 的任务
由于浏览器 GUI 渲染线程与 js 引擎线程是互斥的关系,当页面中有很多长任务时,会造成页面 UI 阻塞。出现界面卡顿情况
查看页面的长任务:
打开控制台,选择 Performance 工具,点击 start 按钮,展开 Main 选项,会发现有很多红色的三角,这些就属于长任务
Web Worker的通信时长
并不是执行时间超过 50ms 的任务,就可以使用 Web Worker, 还需要考虑通信时长的问题,假如一个运算执行时长为100ms ,但是通信时长为300ms ,用了 Web Worker 可能会更慢。
所以当任务的运算时长 减去 通信时长 大于 50ms ,可以考虑使用 Web Worker
8 requestAnimationFrame 制作动画
requestAnimationFrame 是浏览器专门为动画提供的 API , 它的刷新频率与显示器的频率保持一致,使用该 API 以解决用 setTimeout/setInterval 制作动画卡顿的情况
requestAnimationFrame 和 setTimeout /setInterval 的区别
引擎层面
setTimeout /setInterval 属于 js 引擎, requestAnimationFrame 属于 GUI 引擎。 js 引擎与GUI 引擎 是互斥的,也就是说 GUI 引擎在渲染时会阻塞JS 引擎的计算
时间准确性
requestAnimationFrame 刷新频率是固定且准确的,但 setTimeout /setInterval 是宏任务,根据时间轮询机制,其他任务会阻塞或延迟 js 任务的执行,会出现定时器不准的情况
性能方面
当页面被隐藏或最小化时,setTimeout /setInterval 定时器仍会在后台执行动画任务,而使用 requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停
9 优化js加载方式
js一般加载模式
1<script src="./index.js"></script>
这种情况下 js 会阻塞 dom 渲染,浏览器必须等待 index.js 加载和执行完成后才能去做其他事情
async 模式
1<script async src="./index.js"></script>
async 模式下,他是异步加载的,js 不会阻塞 DOM 的渲染, async 加载是无顺序的,当他加载结束,js 会立即执行
defer 模式
1<script defer src="./index.js"></script>
defer 模式下, js 的加载也是异步的,defer 资源会在 DOMContentLoaded 执行之前且defer有顺序的加载
如果有多个设置了 defer 的 script 标签,则会按照引入的前后顺序执行,即便是后面的script 资源先返回
module 模式
1<script type="module">
2 import { a } from './a.js'
3</script>
在当代主流浏览器中, script 标签的属性可以加上 type=“module”,浏览器会对其内部的 import 引用发起 HTTP 请求,或者模块内容。这时 script 的行为会像是 defer 一样,在后台下载并且等待 DOM 解析
preload
link 标签的 preload 属性:用于提前加载一些需要的依赖,这些资源会优先加载。
1<link rel="preload" as="script" href="index.js">
2
3
4 config.plugin('preload').tap(() => [
5 {
6 rel: 'preload',
7
8
9 fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
10 include: 'initial'
11 }
12 ])
vue2 项目打包生成的 index.html 文件,会自动给首页所有需要的资源,全部添加 preload ,实现关键资源的提前加载
preload 特点
- preload加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 onload 事件
- preload加载的js 脚本其加载和执行的过程是分离的 既 preload 会预加载相应的脚本代码,待到需要时自行调用
prefetch
1<link rel="prefetch" as="script" href="index.js">
prefetch 是利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制;通常可以用于加载其他页面(非首页)所需要的资源,以便加快后续页面的打开速度。
prefetch特点
- prefetch 加载的资源可以获取非当前页面所需要的资源,并且将其放入缓存至少5分钟(无论资源是否可以缓存)
- 当页面跳转时,未完成的 prefetch 请求不会被中断
10 图片优化
图片的动态裁剪
很多云服务,比如阿里云或七牛云阿里云或七牛云 都提供了图片的动态裁剪功能,效果很好,缺点需要花钱
只需在图片的url地址上动态添加参数,就可以得到你所需要的尺寸大小,比如:http://7xkv1q.com1.z0.glb.clouddn.com/grape.jpg?imageView2/1/w/100/h/100
图片懒加载
由于浏览器会自动对页面中的 img 标签的 src 属性发送请求并下载图片,可以通过 HTML5 自定义属性 data-xxx 先暂存 src 的值,然后再图片出现在屏幕可视窗口的时候,再将data-xxx的值重新赋值到 img 的src 属性即可
1
2yarn add vue-lazyload --save
3
4import VueLazyload from 'vue-lazyload'
5
6
7const loadimage = require('assets/img/common/loading.gif')
8
9Vue.use(VueLazyload, {
10 preLoad: 1.3,
11 loading: loadimage,
12
13 attempt: 1,
14})
15
16
17
18<ul>
19 <li v-for="img in list">
20 <img v-lazy="img.src" :key="img.src" >
21 </li>
22</ul>
字体图标
图片转 base64