常见首屏性能优化技巧
路由懒加载
要实现懒加载,就得先将进行懒加载的子模块分离出来,打包成一个单独的文件 ES6 的动态地加载模块——import()。 webpackChunkName 作用是 webpack 在打包的时候,对异步引入的库代码(lodash)进行代码分割时,设置代码块的名字。 webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中
组件懒加载
延迟加载组件,只在需要时才进行加载,以提高应用程序的初始加载速度。
tree-sharking
依赖于 ES6 的模块特性,ES6 模块依赖关系是确定的,和运行时的状态无关,可以进行可靠的静态分析,这就是 tree-shaking
的基础
静态分析就是不需要执行代码,就可以从字面量上对代码进行分析。ES6 之前的模块化,比如 CommonJS 是动态加载,只有执行后才知道引用的什么模块,就不能通过静态分析去做优化,正是基于这个基础上,才使得 tree-shaking 成为可能
web-worker
Web Worker
是 HTML5 提供的一种在后台运行的 JavaScript 线程,它可以在主线程之外运行,并且独立于页面的交互,用于执行耗时的计算或处理大量数据而不阻塞主线程,从而提高页面的响应性能。
通过合理地使用 Web Worker,可以充分利用浏览器的多线程能力,将耗时的任务放在后台处理,减少主线程的负担,提高页面的性能和响应速度。
使用步骤
- 创建 web worker:在主线程中使用 new Worker() 构造函数创建一个 Web Worker 对象,并指定要执行的 JavaScript 文件路径。例如:const worker = new Worker('worker.js');
- 编写 Worker 脚本:在指定的 JavaScript 文件中编写 Worker 的脚本代码。Worker 脚本将在后台运行,独立于主线程。
- 监听消息:在主线程中使用 worker.onmessage 事件监听来自 Worker 的消息。当 Worker 完成任务并发送消息时,可以在该事件处理程序中进行处理。例如:worker.onmessage = function(event) { /_ 处理来自 Worker 的消息 _/ };
- 发送消息:在 Worker 脚本中,可以使用 postMessage() 方法向主线程发送消息。主线程将在 onmessage 事件中接收到该消息,并进行相应处理。
Web Worker 可以优化页面数据加载的方式主要体现在以下几个方面:
- 多线程计算:将复杂的计算任务放在 Web Worker 中进行处理,可以避免阻塞主线程,提高页面的响应性能。例如,对大型数据集进行排序、搜索或处理时,可以将这些计算任务交给 Web Worker 来处理,同时不会影响用户与页面的交互。
- 异步数据加载:使用 Web Worker 可以在后台加载和处理数据,而不阻塞主线程的执行。例如,可以将数据的解析、处理和转换工作放在 Web Worker 中进行,然后将处理完成的结果发送给主线程进行显示。
- 并行下载:当页面需要同时下载多个资源时,可以使用多个 Web Worker 并行地进行下载操作,以提高数据加载的效率。每个 Worker 负责下载一个资源,并将下载完成的结果发送给主线程进行处理。
缺点:由于 Web Worker 运行在独立的线程中,并且没有访问 DOM 的能力,因此在 Worker 脚本中无法直接操作 DOM。如果需要与 DOM 进行交互,可以通过消息传递的方式将数据发送到主线程,由主线程进行相应的 DOM 操作。
requestAnimationFrame 制作动画
requestAnimationFrame 是浏览器专门为动画提供的 API,它的刷新频率与显示器的频率保持一致,使用该 api 可以解决用 setTimeout/setInterval 制作动画卡顿的情况
- 区别:
- setTimeout/setInterval 属于 JS 引擎,requestAnimationFrame 属于 GUI 引擎,JS 引擎与 GUI 引擎是互斥的,也就是说 GUI 引擎在渲染时会阻塞 JS 引擎的计算
- requestAnimationFrame 刷新频率是固定且准确的,但 setTimeout/setInterval 是宏任务,根据事件轮询机制,其他任务会阻塞或延迟 js 任务的执行,会出现定时器不准的情况
- 当页面被隐藏或最小化时,setTimeout/setInterval 定时器仍会在后台执行动画任务,而使用 requestAnimationFrame 当页面处于未激活的状态下,屏幕刷新任务会被系统暂停
script 加载模式
- 正常模式 阻塞加载
<script src='index.js'></script>
- async 模式 加载是异步的,JS 不会阻塞 DOM 的渲染,async 加载是无顺序的,当它加载结束,JS 会立即执行 若该 JS 资源与 DOM 元素没有依赖关系,也不会产生其他资源所需要的数据时,可以使用 async 模式,比如埋点统计
<script async src='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='index.js'></script>
- module 模式 在主流的现代浏览器中,script 标签的属性可以加上 type="module",浏览器会对其内部的 import 引用发起 HTTP 请求,获取模块内容。 这时 script 的行为会像是 defer 一样,在后台下载,并且等待 DOM 解析 Vite 就是利用浏览器支持原生的 es module 模块,开发时跳过打包的过程,提升编译效率
<script type='module'>import {a} from './test.js'</script>
preload 预加载模式 link 标签的 preload 属性:用于提前加载一些需要的依赖,这些资源会优先加载 preload 加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞 onload 事件 preload 加载的 JS 脚本其加载和执行的过程是分离的,即 preload 会预加载相应的脚本代码,待到需要时自行调用;
preload-webpack-plugin 插件, 结合 htmlWebpackPlugin 在构建过程中插入 link 标签
// 显式声明一个高优先级资源,强制浏览器提前请求资源,同时不阻塞文档正常onload。
<link as='style' href='css/xxx.css' ref='preload' />
const PreloadWebpackPlugin = require('preload-webpack-plugin');
...
plugins: [
new PreloadWebpackPlugin({
rel: 'preload',
as(entry) { //资源类型
if (/\.css$/.test(entry)) return 'style';
if (/\.woff$/.test(entry)) return 'font';
if (/\.png$/.test(entry)) return 'image';
return 'script';
},
include: 'asyncChunks', // preload模块范围,还可取值'initial'|'allChunks'|'allAssets',
fileBlacklist: [/\.svg/] // 资源黑名单
fileWhitelist: [/\.script/] // 资源白名单
})
]
// 需要注意的是include属性。该属性默认取值'asyncChunks',表示仅预加载异步js模块;如果需要预加载图片、字体等资源,则需要将其设置为'allAssets',表示处理所有类型的资源。
// 但一般情况下我们不希望把预加载范围扩得太大,所以需要通过fileBlacklist或fileWhitelist进行控制。
// 对于异步加载的模块,还可以通过webpack内置的/_ webpackPreload: true _/标记进行更细粒度的控制。
- prefetch 预提取模式 prefetch 是利用浏览器的空闲时间,加载页面将来可能用到的资源的一种机制;通常可以用于加载其他页面(非首页)所需要的资源,以便加快后续页面的打开速度 prefetch 加载的资源可以获取非当前页面所需要的资源,并且将其放入缓存至少 5 分钟(无论资源是否可以缓存) 当页面跳转时,未完成的 prefetch 请求不会被中断
<link rel="prefetch" as="script" href="index.js">
- 总结
async、defer 是 script 标签的专属属性,对于网页中的其他资源,可以通过 link 的 preload、prefetch 属性来预加载 现代框架已经将 preload、prefetch 添加到打包流程中了,通过灵活的配置,去使用这些预加载功能,同时我们也可以审时度势地向 script 标签添加 async、defer 属性去处理资源,这样可以显著提升性能
preload 和 prefetch 的本质都是预加载,即先加载、后执行,加载与执行解耦。 preload 和 prefetch 不会阻塞页面的 onload。 preload 用来声明当前页面的关键资源,强制浏览器尽快加载;而 prefetch 用来声明将来可能用到的资源,在浏览器空闲时进行加载。 preload 的字体资源必须设置 crossorigin 属性,否则会导致重复加载。原因是如果不指定 crossorigin 属性(即使同源),浏览器会采用匿名模式的 CORS 去 preload,导致两次请求无法共用缓存。 没有合法 https 证书的站点无法使用 prefetch,预提取的资源不会被缓存(实际使用过程中发现,原因未知)。
图片优化
CDN 动态裁剪
图片懒加载
图片转 Base64
Vue 中使用 web-worker
创建 Web Worker 文件夹, 在项目中创建一个 Web Worker 的 JavaScript 文件,例如 worker.js
在 Vue 组件中引入 Web Worker 文件:在需要使用 Web Worker 的 Vue 组件中,使用 import 或 require 语句引入 Web Worker 文件。例如:import Worker from '@/path/to/worker.js';
创建 Web Worker 实例:在 Vue 组件中创建一个 Web Worker 实例,可以在 created 钩子函数中进行创建。例如:
jscreated() { this.worker = new Worker(); }
监听消息和错误:在 Vue 组件中监听来自 Web Worker 的消息和错误。可以在 mounted 钩子函数中添加监听器。例如:
jsmounted() { this.worker.onmessage = this.handleWorkerMessage; this.worker.onerror = this.handleWorkerError; } methods: { handleWorkerMessage(event) { // 处理来自 Web Worker 的消息 }, handleWorkerError(error) { // 处理 Web Worker 的错误 }, }
发送消息给 Web Worker:在需要向 Web Worker 发送消息的地方,调用 postMessage() 方法发送消息。例如:
jsthis.worker.postMessage(data)
在 Web Worker 中处理任务:在 Web Worker 文件中编写需要在后台执行的任务代码。 可以监听来自主线程的消息,使用 onmessage 事件处理程序接收消息,并通过 postMessage() 方法将处理结果发送回主线程。例如:
jsself.onmessage = function (event) { // 处理接收到的消息 const result = doWork(event.data) // 将处理结果发送回主线程 self.postMessage(result) }
通过以上步骤,你可以在 Vue 组件中使用 Web Worker 来执行一些耗时的任务,将任务放在后台线程中处理,避免阻塞主线程,提高页面的性能和响应速度。 需要注意的是,由于 Web Worker 运行在独立的线程中,无法直接访问 Vue 组件实例的数据和方法。 如果需要在 Web Worker 中访问 Vue 组件的数据,可以将数据作为消息发送给 Web Worker,在 Web Worker 中处理后将结果发送回主线程,然后在 Vue 组件中进行相应的更新。