首页/文章/性能优化

前端性能优化_白屏-首屏加载

2024-01-20
396014 分钟
...

1 路由懒加载

SPA单页面应用项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有资源,造成首页加载很慢,降低用户体验。(前后大概降低30%)




{
  name: 'personHomeHome',
  path: '/personHome/home',
  meta: {
    title: '个人首页',
    breadcrumb: false,
    dot: false,
    icon: 'iconfont iconzhuye1',
  },
  component: (resolve) => require(['@/views/personHome/home/home.vue'], resolve),
},

{
  path: 'editor',
  meta: { 
    requireLogin: true,
    istrun:true 
  },
  component: () => import( '@/views/form/editor')
},

2 组件懒加载

除了路由的懒加载外,组件的懒加载在很多场景下也有重要的作用

例如:当用户打开某个页面,会一次性加载该页面所有的资源,我们期望的是有些组件使用户触发按钮后,再加载该弹窗组件的资源,例如弹窗组件,这时候,就可以考虑用懒加载的方式引入


import treeTable from "@/components/table/treeTable.vue";
import editDialog from "./editDialog.vue";
export default {
  components: {
    treeTable,
    editDialog,
  },
}

const editDialog = ()=> import( "./editDialog.vue")
const treeTable = ()=> import( "@/components/table/treeTable.vue");
export default {
  components: {
    treeTable,
    editDialog,
  },
}

此时打包之后,弹窗组件就被单独打包出来editDialog对应的js 和css ,当用户点击按钮时,才会去加载。

组件懒加载的场景:

  • 该页面的js文件体积大,导致页面打开很慢,可以通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度。
  • 该组件不是已进入页面就展示,需要一定条件下才触发(例如弹窗组件)
  • 该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 js 文件大小(比如表格组件、图形组件)

3 优化分包策略

vue-cli3 的默认优化是将所有npm依赖都打进 chunk-vendor,但这种做法在依赖多的情况下导致 chunk-vendor 过大

splitChunks 优化分包

 configureWebpack: (config) => {
    if (process.env.NODE_ENV === 'production') {
    config.optimization.splitChunks({
              chunks: 'all',
              cacheGroups: {
                libs: { 
                  name: 'chunk-libs', 
                  test: /[\\/]node_modules[\\/]/, 
                  priority: 10, 
                  chunks: 'initial' 
                },
                elementUI: {
                  name: 'chunk-elementUI', 
                  priority: 20, 
                  test: /[\\/]node_modules[\\/]_?element-ui(.*)/ 
                },
                commons: {
                  name: 'chunk-commons',
                  test: resolve('src/components'), 
                  minChunks: 3, 
                  priority: 5,
                  reuseExistingChunk: true
                }
              }
            })
        }
        
        config.optimization.runtimeChunk('single') 
      }

externals 配合CDN 加速

用 webpack 的externals 属性把把不需要打包的库文件分离出去,减少打包后的文件的大小,用cdn 资源的方式将第三方依赖包引入项目,可以大大减少项目打包体积


vue@2.7.14


key就是 import AAA from 'BBB'中的 BBB
value就是可以在引入CDN 资源后在控制台打印 window.XXX, value 就是 XXX

@1 CDN 引入vue
如果npm 已安装了vue可以选择卸载不卸载也行但是 vue-template-compiler 需要安装注意版本号和vue 版本保持一致
yarn add vue-template-compiler@2.7.14 -S

public文件夹里的index.html 文件引入vue
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
@2 webpack.config.js中的配置

chainWebpack (config) {
    config.externals = { 
      vue: 'Vue',
    },
}

原理是通过 cdn 引入的vue 会挂载到 window 下面window.Vue

4 合理使用 Tree shaking

Tree shaking的作用:消除无用的js 代码,减少代码体积

例如:

export function isFunction(obj) {
        return typeof obj === "function" &&
            typeof obj.nodeType !== "number" &&
            typeof obj.item !== "function";
    };
export function isPlainObject(obj) {
        let proto, Ctor;
        if (!obj || toString.call(obj) !== "[object Object]") return false;
        proto = Object.getPrototypeOf(obj);
        if (!proto) return true; 
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
        return typeof Ctor === "function" && Ctor === Object;
    };

项目中只使用 isFunction 没有使用 isPlainObject 方法,项目打包后,isPlainObject 方法不会被打包到项目里

Tree shaking原理:

依赖于 ES6的模块特性, ES6 模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析这是 Tree shaking的基础

静态分析就是不需要执行代码,就可以从字面量上对代码进行分析。 ES6 之前的模块,比如 CommonJs 是动态加载,只有执行后才可以引用的什么模块,就不能通过静态分析去做优化,正是基于这个基础上,才使得 Tree shaking 成为可能

Tree shaking并不是万能的

并不是所有无用的代码都可以被消除,还是上面的代码,换个写法 Tree shaking 就失效了

export default { 
    isFunction(obj) {
        return typeof obj === "function" &&
            typeof obj.nodeType !== "number" &&
            typeof obj.item !== "function";
    },
    isPlainObject(obj) {
        let proto, Ctor;
        if (!obj || toString.call(obj) !== "[object Object]") return false;
        proto = Object.getPrototypeOf(obj);
        if (!proto) return true; 
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
        return typeof Ctor === "function" && Ctor === Object;
    },
  }

同样的,项目中只使用 isFunction 没有使用 isPlainObject 方法,项目打包后,isPlainObject 方法还是被打包到项目里,

原因是 export default 导出的是一个对象,无法通过静态分析判断出一个对象的哪些变量未被使用,所以 tree shaking 只对使用 export 单个导出的变量生肖

这也是函数式编程越来越被青睐的原因,因为可以很好利用 tree-shaking 精简项目的体积

5 骨架屏优化白屏时长

SPA单页面应用,无论是 vue 还是 react ,最初的 html 都是空白的,需要通过加载 js 将内容挂载到跟节点上,这套机制的副作用:会造成长时间的白屏

常见的骨架屏插件就是基于这种原理,在项目打包时,将骨架屏的内容直接放到 html 文件的根节点中

骨架屏插件 vue-skeleton-webpack-plugin 插件为例,该插件的亮点时可以给不同的页面设置不同的骨架屏


yarn add vue-skeleton-webpack-plugin



const SkeletonWebpackPlugin = require("vue-skeleton-webpack-plugin");
module.exports = {
   configureWebpack: {
      plugins: [
       new SkeletonWebpackPlugin({
        
        webpackConfig: {
          entry: {
            app: path.join(__dirname, './src/skeleton.js') 
          }
        },
        minimize: true, 
        quiet: true, 
        router: {
          mode: 'hash', 
          routes: [
            
            
            { path: /^\/home(?:\/)?/i, skeletonId: 'homeSkeleton' },
            { path: /^\/detail(?:\/)?/i, skeletonId: 'detailSkeleton' }
          ]
        }
      })        
      ]
   }
}


import 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>
  `,
});

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 的使用


yarn add vue-virtual-scroller


import VueVirtualScroller from 'vue-virtual-scroller' 
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' 


Vue.use(VueVirtualScroller)


import Vue from 'vue'
import { RecycleScroller } from 'vue-virtual-scroller'
Vue.component('RecycleScroller', RecycleScroller)


<template> 
  <RecycleScroller 
    class="scroller" 
    :items="list" 
    :item-size="32" 
    key-field="id" 
    v-slot="{ item }"> 
      <div class="user"> {{ item.name }} </div>
  </RecycleScroller> 
</template>

该插件主要有 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一般加载模式

<script src="./index.js"></script>

这种情况下 js 会阻塞 dom 渲染,浏览器必须等待 index.js 加载和执行完成后才能去做其他事情

async 模式

<script async src="./index.js"></script>

async 模式下,他是异步加载的,js 不会阻塞 DOM 的渲染, async 加载是无顺序的,当他加载结束,js 会立即执行

defer 模式

<script defer src="./index.js"></script>

defer 模式下, js 的加载也是异步的,defer 资源会在 DOMContentLoaded 执行之前且defer有顺序的加载

如果有多个设置了 defer 的 script 标签,则会按照引入的前后顺序执行,即便是后面的script 资源先返回

module 模式

<script type="module">
 import { a } from './a.js'
</script>

在当代主流浏览器中, script 标签的属性可以加上 type="module",浏览器会对其内部的 import 引用发起 HTTP 请求,或者模块内容。这时 script 的行为会像是 defer 一样,在后台下载并且等待 DOM 解析

preload

link 标签的 preload 属性:用于提前加载一些需要的依赖,这些资源会优先加载。

<link rel="preload" as="script" href="index.js">


    config.plugin('preload').tap(() => [
      {
        rel: 'preload',
        
        
        fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
        include: 'initial'
      }
    ])

vue2 项目打包生成的 index.html 文件,会自动给首页所有需要的资源,全部添加 preload ,实现关键资源的提前加载

preload 特点

  • preload加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 onload 事件
  • preload加载的js 脚本其加载和执行的过程是分离的 既 preload 会预加载相应的脚本代码,待到需要时自行调用

prefetch

<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 属性即可


yarn add vue-lazyload --save

import VueLazyload from 'vue-lazyload'


const loadimage = require('assets/img/common/loading.gif')

Vue.use(VueLazyload, {
  preLoad: 1.3, 
  loading: loadimage, 
  
  attempt: 1, 
})



<ul>  
    <li v-for="img in list">
        <img v-lazy="img.src" :key="img.src" >
    </li>
</ul>

字体图标

图片转 base64

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

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

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
性能优化2025-08-03
前端性能优化实战:从页面加载到交互响应的全链路优化
性能优化的核心指标 在开始优化之前,我们先要明确几个核心指标: 1\. FP (First Paint) 首次绘制 用户看到第一个像素的时间。 2\. FCP (First Contentful Paint) 首次内容绘制 用户看到第一个有意义内容的时间。 3\. LCP (Largest Contentful Paint) 最大内容绘制 页面最大内容元素渲染完成的时间。 4\. FID (First Input Delay) 首次输入...
学习面试
性能优化2025-07-21
吃透前端项目优化系列(一):从构建提速开始,分节拆解工程化方案
引言 前端项目越做越大,加载慢、打包久、体验差成了通病?别慌,这个系列会分节拆解 7 大优化方向,从工程化角度帮你逐个击破。第一节先聚焦构建与打包优化,用 Vite 和 Webpack 的实战技巧提速,后续还会解锁其他优化模块,关注追更不迷路~ 优化不是一次性动作,而是分阶段深耕的过程 —— 跟着系列文章一步步来,让项目性能持续升级。 开始 🚀整个系列将按 7 大优化方向逐步展开,每节聚焦一个核心模块,帮你系统掌握前端项目优化: |...
学习面试
性能优化2025-07-21
吃透前端项目优化系列(二):首屏渲染优化 + 性能指标拆解首屏加载慢、性能指标看不懂
引言 首屏加载慢 1 秒,用户流失率可能上涨 20%!这节专门解决 “首屏渲染” 和 “性能指标” 两大痛点 —— 从资源加载怎么排优先级,到页面渲染阻塞怎么破,再到核心 Web 指标怎么看,连 Vue3 特有的编译优化都拆成了实操步骤,帮你让页面加载又快又稳~ 性能优化不是 “凭感觉提速”,而是看懂指标、精准发力 开始 本节聚焦第二模块,专注首屏渲染与性能指标: | 序号 | 优化方向 | 本节聚焦 | 核心价值 | | | | |...
学习面试
nuxt2025-10-22
Nuxt SSR 与 Next.js SSR
🧭 引言 在现代前端生态中, Nuxt(Vue) 与 Next.js(React) 是最成熟的两大全栈框架。 它们都具备 SSR(Server Side Rendering) 能力,但实现原理与运行机制差异巨大。 如果你在使用 Nuxt 构建 SSR 项目,却发现“路由跳转后不再 SSR”, 而在 Next.js 中同样操作却会重新发起服务端渲染——这不是 bug,而是 理念差异 。 本文将详细对比这两种框架的 SSR 行为,帮助你彻...
学习面试
vue2025-10-22
shallowRef 与 ref 的区别、场景与坑位
结论先行: 会 深度 地把你放进去的对象转成响应式(递归代理),因此"对象内部的改动"也会触发视图更新。 只在 顶层 做依赖追踪: 更换 才触发更新 ;若你只是"在原对象上改属性",不会触发,除非手动 。 1\. 快速对比 维度 追踪粒度 深度(递归) 仅顶层 对象内部属性变更 会触发更新 不会 触发(除非 或重新赋值) 性能 有递归 & 依赖跟踪开销 更轻量,适合大对象/第三方实例 适用对象 普通标量、普通对象、数组 第三方实例(图表...
学习面试
vue2025-10-20
Vue 3 中的 setup执行时机与异步逻辑详解
Vue 3 中的 执行时机与异步逻辑详解 本文详细介绍 Vue 3 组合式 API 的核心函数 :它在组件生命周期中的执行时机、异步行为( 的影响)、常见用法和最佳实践。 一、 的执行时机 是 Vue 3 组件实例创建后、渲染前执行的函数。 它相当于 Vue 2 中的 和 的合体。 执行流程: 也就是说: 所有响应式数据( 、 、 )都在这里定义; 所有生命周期钩子( 、 等)都在这里注册; 模板渲染会等待 执行完成(或异步返回的 Pr...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录