首页IT科技前端页面优化解决方法(记录–前端性能优化——首页资源压缩63%、白屏时间缩短86%)

前端页面优化解决方法(记录–前端性能优化——首页资源压缩63%、白屏时间缩短86%)

时间2025-05-03 12:04:37分类IT科技浏览3328
导读:这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助...

这里给大家分享我在网上总结出来的一些知识           ,希望对大家有所帮助

重新打包后               ,首页资源拆分为 app.js 和 home.js    ,以及对应的 css 文件

app.js:244 KB           、 home.js:35KB

app.css:67 KB               、home.css:15KB

通过路由懒加载        ,该项目的首页资源压缩约52%

路由懒加载的原理

懒加载前提的实现:ES6的动态地加载模块——import()

调用 import() 之处                ,被作为分离的模块起点      ,意思是     ,被请求的模块和它引用的所有子模块                 ,会分离到一个单独的 chunk 中

——摘自《webpack——模块方法》的import()小节

要实现懒加载        ,就得先将进行懒加载的子模块分离出来  ,打包成一个单独的文件

webpackChunkName 作用是 webpack 在打包的时候                 ,对异步引入的库代码(lodash)进行代码分割时           ,设置代码块的名字           。webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中

2    、组件懒加载

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

举个?:

home 页面 和 about 页面              ,都引入了 dialogInfo 弹框组件              ,该弹框不是一进入页面就加载  ,而是需要用户手动触发后才展示出来

home 页面示例:

<template> <div class="homeView"> <p>home 页面</p> <el-button @click="dialogVisible = !dialogVisible">打开弹框</el-button> <dialogInfo v-if="dialogVisible" /> </div> </template> <script> import dialogInfo from @/components/dialogInfo; export default { name: homeView, components: { dialogInfo } } </script>

项目打包后           ,发现 home.js 和 about.js 均包括了该弹框组件的代码(在 dist 文件中搜索dialogInfo弹框组件)

当用户打开 home 页时               ,会一次性加载该页面所有的资源    ,我们期望的是用户触发按钮后        ,再加载该弹框组件的资源

这种场景下                ,就很适合用懒加载的方式引入

弹框组件懒加载:

<script> const dialogInfo = () => import(/* webpackChunkName: "dialogInfo" */ @/components/dialogInfo); export default { name: homeView, components: { dialogInfo } } </script>

重新打包后      ,home.js 和 about.js 中没有了弹框组件的代码     ,该组件被独立打包成 dialogInfo.js                 ,当用户点击按钮时        ,才会去加载 dialogInfo.js 和 dialogInfo.css

最终  ,使用组件路由懒后                 ,该项目的首页资源进一步减少约 11%

组件懒加载的使用场景

有时资源拆分的过细也不好           ,可能会造成浏览器 http 请求的增多

总结出三种适合组件懒加载的场景:

1)该页面的 JS 文件体积大,导致页面打开慢              ,可以通过组件懒加载进行资源拆分              ,利用浏览器并行下载资源  ,提升下载速度(比如首页)

2)该组件不是一进入页面就展示           ,需要一定条件下才触发(比如弹框组件)

3)该组件复用性高               ,很多页面都有引入    ,利用组件懒加载抽离出该组件        ,一方面可以很好利用缓存                ,同时也可以减少页面的 JS 文件大小(比如表格组件        、图形组件等)

3                、合理使用 Tree shaking

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

举个?:

// util.js export 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.js export 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.js 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" /> <detailSkeleton id="detailSkeleton" /> </div> `, });

5     、长列表虚拟滚动

首页中不乏有需要渲染长列表的场景,当渲染条数过多时              ,所需要的渲染时间会很长              ,滚动时还会造成页面卡顿  ,整体体验非常不好

虚拟滚动——指的是只渲染可视区域的列表项           ,非可见区域的不渲染               ,在滚动时动态更新可视区域    ,该方案在优化大量数据渲染时效果是很明显的

虚拟滚动图例:

虚拟滚动基本原理:

计算出 totalHeight 列表总高度        ,并在触发时滚动事件时根据 scrollTop 值不断更新 startIndex 以及 endIndex                 ,以此从列表数据 listData 中截取对应元素

虚拟滚动性能对比:

在不使用虚拟滚动的情况下      ,渲染10万个文本节点:

使用虚拟滚动的情况后:

使用虚拟滚动使性能提升了 78%

虚拟滚动插件

虚拟滚动的插件有很多     ,比如 vue-virtual-scroller                 、vue-virtual-scroll-list        、react-tiny-virtual-list  、react-virtualized 等

这里简单介绍 vue-virtual-scroller 的使用

// 安装插件 npm install vue-virtual-scroller // main.js import VueVirtualScroller from vue-virtual-scroller import vue-virtual-scroller/dist/vue-virtual-scroller.css Vue.use(VueVirtualScroller) // 使用 <template> <RecycleScroller class="scroller" :items="list" :item-size="32" key-field="id" v-slot="{ item }"> <div class="user"> {{ item.name }} </div> </RecycleScroller> </template>

该插件主要有 RecycleScroller.vue                 、DynamicScroller.vue 这两个组件                 ,其中 RecycleScroller 需要 item 的高度为静态的        ,也就是列表每个 item 的高度都是一致的  ,而 DynamicScroller 可以兼容 item 的高度为动态的情况

6           、Web Worker 优化长任务

由于浏览器 GUI 渲染线程与 JS 引擎线程是互斥的关系                 ,当页面中有很多长任务时           ,会造成页面 UI 阻塞,出现界面卡顿、掉帧等情况

查看页面的长任务:

打开控制台              ,选择 Performance 工具              ,点击 Start 按钮  ,展开 Main 选项           ,会发现有很多红色的三角               ,这些就属于长任务(长任务:执行时间超过50ms的任务)

测试实验:

如果直接把下面这段代码直接丢到主线程中    ,计算过程中页面一直处于卡死状态        ,无法操作

let sum = 0; for (let i = 0; i < 200000; i++) { for (let i = 0; i < 10000; i++) { sum += Math.random() } }

使用 Web Worker 执行上述代码时                ,计算过程中页面正常可操作              、无卡顿

// worker.js onmessage = 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 制作动画卡顿的情况

下面的案例演示了两者制作进度条的对比(运行按钮可点击)

可以看到使用定时器制作的动画  ,卡顿还是比较明显的

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="https://www.cnblogs.com/smileZAZ/archive/2023/03/02/index.js"></script>

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

2)async 模式

<script async src="https://www.cnblogs.com/smileZAZ/archive/2023/03/02/index.js"></script>

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

使用场景:若该 JS 资源与 DOM 元素没有依赖关系  ,也不会产生其他资源所需要的数据时           ,可以使用async 模式               ,比如埋点统计

3)defer 模式

<script defer src="https://www.cnblogs.com/smileZAZ/archive/2023/03/02/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="https://www.cnblogs.com/smileZAZ/archive/2023/03/02/vue.js"></script> <script defer src="https://www.cnblogs.com/smileZAZ/archive/2023/03/02/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="https://www.cnblogs.com/smileZAZ/archive/2023/03/02/index.js">

link 标签的 preload 属性:用于提前加载一些需要的依赖        ,这些资源会优先加载(如下图红框)

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

preload 特点:

1)preload 加载的资源是在浏览器渲染机制之前进行处理的                 ,并且不会阻塞 onload 事件;

2)preload 加载的 JS 脚本其加载和执行的过程是分离的        ,即 preload 会预加载相应的脚本代码  ,待到需要时自行调用;

6)prefetch

<link rel="prefetch" as="script" href="https://www.cnblogs.com/smileZAZ/archive/2023/03/02/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

图片瘦身前后对比:

原图:1.8M 裁剪后:12.8KB

经过动态裁剪后的图片  ,加载速度会有非常明显的提升

图片的懒加载

对于一些图片量比较大的首页           ,用户打开页面后               ,只需要呈现出在屏幕可视区域内的图片    ,当用户滑动页面时        ,再去加载出现在屏幕内的图片                ,以优化图片的加载效果

图片懒加载实现原理:

由于浏览器会自动对页面中的 img 标签的 src 属性发送请求并下载图片      ,可以通过 html5 自定义属性 data-xxx 先暂存 src 的值     ,然后在图片出现在屏幕可视区域的时候                 ,再将 data-xxx 的值重新赋值到 img 的 src 属性即可

<img src="" alt="" data-src="https://www.cnblogs.com/smileZAZ/archive/2023/03/02/images/1.jpg"> <img src="" alt="" data-src="https://www.cnblogs.com/smileZAZ/archive/2023/03/02/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:

// 安装 npminstallurl-loader--save-dev // 配置 module.exports={ module:{ rules:[{ test:/.(png|jpg|gif)$/i, use:[{ loader:url-loader, options:{ // 小于 10kb 的图片转化为 base64 limit:1024 * 10 } }] }] } };

优化总结

本文主要介绍的是 代码层面 的性能优化  ,经过上面的一系列优化                 ,首页打开速度有了明显的提升           ,虽然都是一些常规方案,但其中可以深挖的知识点并不少

下一篇文章: 前端内存优化知多少?内存泄露只是冰山一角              ,聊一聊前端内存方面的优化              ,小伙们一起来看看其中又有哪些知识点

本文转载于:

https://juejin.cn/post/7188894691356573754

如果对您有所帮助  ,欢迎您点个关注           ,我会定时更新技术文档               ,大家一起讨论学习    ,一起进步     。

声明:本站所有文章        ,如无特殊说明或标注                ,均为本站原创发布                 。任何个人或组织      ,在未征得本站同意时     ,禁止复制           、盗用、采集              、发布本站内容到任何网站              、书籍等各类媒体平台        。如若本站内容侵犯了原著者的合法权益                 ,可联系我们进行处理  。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
python全国青少年竞赛题目(首届Pythonic Hackathon 竞赛!_豆瓣) 浙江爱因宝母婴公司(义乌市爱因宝母婴用品有限公司_公司简介)