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

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

2、组件懒加载

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

举个:

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

home 页面示例:


<script>
import dialogInfo from '@/components/dialogInfo';
export default {
  name: 'homeView',
  components: {
    dialogInfo
  }
}
script>

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

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

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

弹框组件懒加载:

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

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

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

组件懒加载的使用场景

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

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

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

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

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

3、合理使用 Tree

Tree 的作用:消除无用的 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));
}

项目中只使用了 方法,但未使用 方法,项目打包后, 方法不会被打包到项目里

tree- 原理:

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

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

Tree 并不是万能的

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

// 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)

同样的,项目中只使用了 方法,未使用 方法,项目打包后, 方法还是被打包到项目里

在 dist 文件中搜索 方法:

究其原因, 导出的是一个对象,无法通过静态分析判断出一个对象的哪些变量未被使用,所以 tree- 只对使用 导出的变量生效

这也是函数式编程越来越火的原因,因为可以很好利用 tree- 精简项目的体积,也是 vue3 全面拥抱了函数式编程的原因之一

4、骨架屏优化白屏时长

使用骨架屏,可以缩短白屏时间,提升用户体验。国内大多数的主流网站都使用了骨架屏,特别是手机端的项目

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

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

使用骨架屏插件,打包后的 html 文件(根节点内部为骨架屏):

同一项目,对比使用骨架屏前后的 FP 白屏时间:

骨架屏确实是优化白屏的不二选择,白屏时间缩短了 86%

骨架屏插件

这里以 vue--- 插件为例,该插件的亮点是可以给不同的页面设置不同的骨架屏,这点确实很酷

1)安装

npm i vue-skeleton-webpack-plugin 

2)vue..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)新建 .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: `
    
"homeSkeleton" style="display:none;" /> "detailSkeleton" style="display:none;" />
`, })
;

5、长列表虚拟滚动

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

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

虚拟滚动图例:

虚拟滚动基本原理:

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

虚拟滚动性能对比:

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

虚拟滚动插件

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

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

// 安装插件
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)
// 使用

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

6、Web 优化长任务

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

查看页面的长任务:

打开控制台,选择 工具,点击 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.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 具体的使用与案例,详情见 一文彻底了解Web ,十万、百万条数据都是弟弟

Web 的通信时长

并不是执行时间超过 50ms 的任务,就可以使用 Web ,还要先考虑通信时长的问题

假如一个运算执行时长为 100ms,但是通信时长为 300ms, 用了 Web 可能会更慢

比如新建一个 web , 浏览器会加载对应的 .js 资源,下图中的 Time 是这个资源的通信时长(也叫加载时长)

当任务的运算时长 - 通信时长 > 50ms,推荐使用Web

7、e 制作动画

e 是浏览器专门为动画提供的 API,它的刷新频率与显示器的频率保持一致,使用该 api 可以解决用 / 制作动画卡顿的情况

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

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

/、e 三者的区别:

1)引擎层面

/ 属于 JS引擎,e 属于 GUI引擎

JS引擎与GUI引擎是互斥的,也就是说 GUI 引擎在渲染时会阻塞 JS 引擎的计算

2)时间是否准确

e 刷新频率是固定且准确的,但 / 是宏任务,根据事件轮询机制,其他任务会阻塞或延迟js任务的执行,会出现定时器不准的情况

3)性能层面

当页面被隐藏或最小化时,/ 定时器仍会在后台执行动画任务,而使用 e 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停

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 资源会在执行之前,并且 defer 是有顺序的加载

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

所以 defer 可以用来控制 JS 文件的执行顺序,比如 -ui.js 和 vue.js,因为 -ui.js 依赖于 vue,所以必须先引入 vue.js,再引入 -ui.js

<script defer src="vue.js">script>
<script defer src="element-ui.js">script>

defer 使用场景:一般情况下都可以使用 defer,特别是需要控制资源加载顺序时

4) 模式

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

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

Vite 就是利用浏览器支持原生的 es 模块,开发时跳过打包的过程,提升编译效率

5)

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

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

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

特点:

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

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

6)

rel="prefetch" as="script" href="index.js">

是利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制;通常可以用于加载其他页面(非首页)所需要的资源,以便加快后续页面的打开速度

特点:

1) 加载的资源可以获取非当前页面所需要的资源,并且将其放入缓存至少5分钟(无论资源是否可以缓存)

2)当页面跳转时,未完成的 请求不会被中断

加载方式总结

async、defer 是 标签的专属属性,对于网页中的其他资源,可以通过link的 、属性来预加载

如今现代框架已经将 、 添加到打包流程中了,通过灵活的配置,去使用这些预加载功能,同时我们也可以审时度势地向 标签添加 async、defer 属性去处理资源,这样可以显著提升性能

9、图片的优化

平常大部分性能优化工作都集中在 JS 方面,但图片也是页面上非常重要的部分

特别是对于移动端来说,完全没有必要去加载原图,浪费带宽。如何去压缩图片,让图片更快的展示出来,有很多优化工作可以做

未经允许不得转载! 作者:admin,转载或复制请以超链接形式并注明出处墨迹游戏网

原文地址:《前端性能优化——首页资源压缩63%、白屏时间缩短86%》发布于:2024-11-27