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

参考博客 blog.csdn.net/muzidigbig/… juejin.cn/post/718889…

骨架屏文章 blog.csdn.net/tangxiujian…

个人笔记记录 2021 ~ 2025