懒加载(或按需加载、动态加载):
在前端应用中具有重要的好处,主要体现在性能优化和用户体验方面。以下是懒加载的一些好处和原理:
好处:
- 减少初始加载时间: 将应用的代码拆分成多个块,并仅在需要时加载,可以减小初始加载时间,提高页面加载性能。
- 降低资源请求量: 页面初次加载时只请求必要的资源,而延迟加载其他模块,从而减少了初始页面请求的资源量。
- 更快的页面响应速度: 当用户导航到新页面时,只加载当前页面所需的代码,使得页面更快地响应用户操作。
- 节省带宽: 通过按需加载,可以减少首次加载时需要下载的资源大小,从而节省用户的带宽。
- 更好的用户体验: 用户只需等待当前页面所需的代码加载完成,而不是等待整个应用的代码加载完成,提升了用户体验。
原理:
- 动态 import: 在 JavaScript 中,使用动态 import 语法可以在运行时异步加载模块。这意味着你可以在代码中按需引入模块,而不是在应用启动时一次性加载所有模块。
- Webpack Code Splitting: Webpack 支持代码拆分(Code Splitting),这是一种将代码分割成更小的块的技术。Webpack 在构建时会根据配置生成多个代码块,每个代码块对应一个按需加载的模块。
- Vue Router 和 defineAsyncComponent(Vue 3.x): 在 Vue.js 中,Vue Router 支持使用动态 import 来进行路由懒加载。Vue 3.x 中,可以使用 defineAsyncComponent 函数将组件定义为异步加载的组件。
- 分割点(Split Points): 在应用中定义分割点,将代码拆分为不同的块。这样,在用户导航到相关页面时,只需加载与该页面相关的代码块。
路由懒加载
路由懒加载是一种优化技术,可以帮助减少初始加载时间,特别是在大型 Vue.js 应用中。它允许你在需要时才加载特定的路由组件,而不是一开始就加载所有路由组件。
vue2
在 Vue Router 中,你可以使用路由懒加载来延迟加载路由组件。在 Vue 2.x 中,通常使用动态 import 来实现路由懒加载。下面是一个示例:
const Home = () => import(/* webpackChunkName: "home" */ './views/Home.vue');
const About = () => import(/* webpackChunkName: "about" */ './views/About.vue');
const routes = [
{
path: '/',
component: Home
},
{
path: '/about',
component: About
}
];
也可以直接写在routes里面
import 是 ECMAScript 模块规范(ES Module)的一部分,而模块的加载是异步的。
当你使用 import 导入一个模块时,浏览器会异步地加载这个模块,并返回一个 Promise 对象。
好处:它允许浏览器在加载其他资源时继续执行 JavaScript 代码,而不会阻塞页面渲染。当模块加载完成后,会触发 Promise 的解析,然后执行相应的回调函数。
const myModulePromise = import('./myModule.js');
myModulePromise
.then((myModule) => {
// 在这里可以使用已加载的模块
})
.catch((error) => {
// 处理加载失败的情况
});
写在routes里:
const routes = [
{
path: '/',
component: () => import(/* webpackChunkName: "home" */ './views/Home.vue');
},
{
path: '/about',
component: () => import(/* webpackChunkName: "about" */ './views/About.vue');
}
];
命名化:
import 语句中的注释 /* webpackChunkName: "home" */
是 Webpack 提供的注释语法,用于给生成的代码块(chunk)命名。这可以帮助你在调试和分析时更清楚地识别异步加载的代码块。
vue3
在 Vue 3.x 中,你可以使用 defineAsyncComponent 函数与 import 结合,以达到相似的效果:
javascriptCopy code
import { defineAsyncComponent } from 'vue';
const Home = defineAsyncComponent(() => import('./views/Home.vue'));
const About = defineAsyncComponent(() => import('./views/About.vue'));
const routes = [
{
path: '/',
component: Home
},
{
path: '/about',
component: About
}
];
这两种方法都允许你在路由需要时按需加载组件。
Webpack 会自动生成单独的代码块,以便在用户访问特定路由时才下载和加载相应的组件,从而优化了初始加载时间。
其他懒加载优化方式
除了路由懒加载,还有一些其他前端资源懒加载的优化方式,以提高应用性能和用户体验。以下是一些常见的优化方式:
1. 图片懒加载: 对于页面上不可见区域的图片,可以延迟加载,直到用户滚动到它们的位置。这可以通过设置 loading="lazy" 属性,或者使用 JavaScript 实现。
原理:
基本的图片懒加载原理是将图片的 src 属性设置为一个占位符或者空字符串,然后使用自定义属性(通常是 data-src 或类似的属性)存储真实图片的 URL。
当图片进入用户的视野时(例如,当用户滚动页面时),再将 data-src 的值赋给 src 属性,触发图片的加载。
例如:
<!-- 原始 img 标签 -->
<img data-src="path/to/real-image.jpg" class="lazyload" alt="Lazy Loaded Image">
1.jsDOM操作图片懒加载的实现
// 获取所有懒加载图片
const lazyImages = document.querySelectorAll('.lazyload');
// 判断图片是否进入视窗,如果是则加载图片
function lazyLoad() {
lazyImages.forEach((img) => {
//getBoundingClientRect().top: 获取元素相对于视口(浏览器窗口)顶部的距离。
//这个值是一个负数,表示元素顶部距离视口顶部的偏移量。
//window.innerHeight: 获取视口的高度,即浏览器窗口可见区域的高度
//这个判断意味着检查图片顶部是否已经进入视口。如果为真,表示图片的顶部已经可见。
if (img.getBoundingClientRect().top < window.innerHeight && img.dataset.src) {
//img.dataset.src:获取图片元素上的 data-src 属性的值。这个属性通常用于存储真实图片的 URL。
img.src = img.dataset.src;
//在图片加载后,将图片元素上的 data-src 属性移除(看上文html部分的代码)
//一旦图片被加载,就不再需要 data-src 属性了,因为图片的实际 src 属性已经被设置为真实的图片地址。
//防止多次的给图片进行加载
img.removeAttribute('data-src');
}
});
}
// 监听滚动事件,在滚动时调用懒加载函数
window.addEventListener('scroll', lazyLoad);
// 页面加载完毕时触发一次懒加载,以加载首屏可见图片
window.addEventListener('DOMContentLoaded', lazyLoad);
注意
dataset.src 中的 dataset 是一个DOM元素的属性,它提供了一个 DOMStringMap 对象(本质就是代表了元素所有data+*属性的合集,因此如果你想使用自定义属性,并希望使用 DOMStringMap 来访问这些属性,那么这些属性的命名必须以 "data-" 作为前缀。这是 HTML5 规范中关于自定义数据属性的标准。),其中包含元素的所有自定义数据属性。在这个上下文中,.src 表示你正在获取元素上名为 data-src 的自定义属性的值。
- dataset: 是一个对象,用于存储元素上所有以 data- 开头的自定义属性。
- .src: 是你具体获取的一个自定义属性的值,其中 data- 后的部分(在这里是 src)就是自定义属性的名称。
2.图片懒加载vue第三方库实现:
安装 vue-lazyload:
bashCopy code
npm install vue-lazyload
然后,在你的Vue项目中,可以按照以下步骤实现图片懒加载:
- 在 main.js(或其他入口文件)中引入并使用 vue-lazyload:
javascriptCopy code
import Vue from 'vue';
import VueLazyload from 'vue-lazyload';
Vue.use(VueLazyload);
- 在组件中使用 v-lazy 指令来实现懒加载:
htmlCopy code
<template>
<div>
<img v-lazy="imageUrl" alt="Lazy Loaded Image">
</div>
</template>
<script>
export default {
data() {
return {
imageUrl: 'path/to/placeholder.jpg', // 初始时设置一个占位图
};
},
};
</script>
在这个例子中,v-lazy 指令将 imageUrl 绑定到图片的 src 属性。初始时,imageUrl 被设置为一个占位图的路径。当图片进入用户的视野时,v-lazy 会自动将 imageUrl 设置为真实图片的路径,触发图片加载。
请注意,v-lazy 指令是 vue-lazyload 提供的功能,它简化了懒加载的实现。使用这种方式,你不需要手动编写 JavaScript 来检测滚动等事件,库会自动处理这些细节。
3.vue手动实现图片懒加载
IntersectionObserver 监听图片元素是否进入视口,当图片进入视口并且之前未加载时,会更新 imageSrc 为真实图片的路径,触发图片加载。
<template>
<div>
<img :src="imageSrc" alt="Lazy Loaded Image" ref="lazyImage">
</div>
</template>
<script>
export default {
data() {
return {
//在实现图片懒加载时,使用占位图的目的是为了提供一个轻量的初始图像,以减少页面加载时对性能的影响。
//占位图通常是一个小尺寸、轻量的图像,它会快速加载,而不会给用户造成过多的等待时间。
imageSrc: 'path/to/placeholder.jpg', // 初始时设置一个占位图
shouldLoad: false,
};
},
mounted() {
//创建 IntersectionObserver 对象
//IntersectionObserver 是一个用于异步监视目标元素与祖先元素或顶级文档视窗交叉状态的 API
//并传递了一个回调函数 this.handleIntersection
const observer = new IntersectionObserver(this.handleIntersection, {
root: null, // 使用默认的视口
threshold: 0.5, // 当目标元素50%可见时触发回调
});
// 开始观察图片元素
//observe 是 IntersectionObserver 对象的方法之一,用于开始观察指定的目标元素
//当被观察的元素进入或离开视口时,相关的回调函数将被触发。
observer.observe(this.$refs.lazyImage);
},
methods: {
handleIntersection(entries) {
// 当图片进入视口时,更新 imageSrc,触发图片加载
//entries: 是 IntersectionObserver 回调函数中传递的参数,它是一个包含有关被观察元素的信息的数组。
//在这里,我们使用 entries[0] 获取第一个(通常也是唯一一个)被观察元素的信息。
//entries[0].isIntersecting: 是一个布尔值,表示被观察元素是否进入了视口。
//如果 isIntersecting 为 true,则表示元素已经进入视口。
//!this.shouldLoad[index]: 是检查一个名为 shouldLoad 数组的状态,此时只有一个图片,没有使用数组形式
//在这里,我们使用 !(逻辑非)来检查 shouldLoad[index] 是否为 false。如果 shouldLoad[index] 为 false,表示这张图片之前未加载,满足这个条件。
if (entries[0].isIntersecting && !this.shouldLoad) {
this.imageSrc = 'path/to/real-image.jpg';
this.shouldLoad = true;
}
},
},
};
</script>
2. 字体懒加载: 如果你使用自定义字体,可以通过异步加载字体文件,以延迟下载时间。可以使用 Web Font Loader 或直接在样式表中使用 @font-face 并设置 font-display: swap;。
3. 组件懒加载: 不仅可以在路由层面进行懒加载,还可以在普通组件层面使用异步组件(Async Components)来实现懒加载。这样可以按需加载页面上其他组件,而不是一次性加载所有组件。
这段内容写在需要使用的地方:
// 使用异步组件
const AsyncComponent = () => ({
// 需要加载的组件。它返回一个 Promise 对象,必写项
component: import('./AsyncComponent.vue'),
// 在组件加载过程中显示的加载组件
loading: LoadingComponent,
// 加载失败时显示的组件
error: ErrorComponent,
// 延迟时间(默认是 200ms), 在这段时间内不会显示 loading 组件
delay: 200,
// 超时时间(默认是 Infinity), 如果加载时间超过此值,则显示 error 组件
timeout: 3000
});
异步组件的定义通常是在需要使用该异步组件的页面或组件中完成。这样做是为了确保按需加载组件,而不是在应用启动时一次性加载所有组件,从而提高应用性能和加载速度。
a. 在路由配置中定义异步组件:
// 路由配置
import { createRouter, createWebHashHistory } from 'vue-router';
// 异步加载组件的函数
const AsyncComponent = () => ({
component: import('./AsyncComponent.vue'),//必写,其他的是可写项
loading: LoadingComponent, // 加载时显示的组件
error: ErrorComponent, // 加载失败时显示的组件
delay: 200, // 延迟显示加载组件的时间
timeout: 1000 // 加载超时时间
});
const routes = [
{
path: '/async',
component: AsyncComponent // 在路由中使用异步组件
}
];
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;
b. 在组件定义中异步加载:
自己尝试之后发现只有defineAsyncComponent是可行的,另外一个方法不知道为什么一直报错。
<template>
<div>
<h1>home</h1>
</div>
<AsyncComponent></AsyncComponent>
</template>
<script>
import {defineAsyncComponent} from "vue"
const AsyncComponent=defineAsyncComponent(()=>import('./async.vue')) // 使用import()函数动态导入组件
export default {
components:{
AsyncComponent
}
};
</script>
<style scoped>
/* 在这里添加自定义的CSS样式 */
</style>
本质
组件懒加载的本质是基于 JavaScript 的动态 import() 函数的异步加载特性。import() 函数返回一个 Promise 对象,该 Promise 在模块加载完成时被解析。(上文有提到过)
4. 按需加载第三方库: 对于大型第三方库,可以按需加载只需要的部分,而不是一次性加载整个库。例如,使用类似于 import() 的动态导入语法。
javascriptCopy code
// 按需加载lodash
const _ = await import('lodash');
5. 条件加载资源: 根据设备类型、网络状况或其他条件动态加载资源。例如,可以根据设备分辨率加载不同大小的图像。
这些优化方式可以根据具体的应用需求和性能瓶颈选择使用。通过懒加载不必要的资源,可以减小初始加载时间,提高页面渲染速度,以及降低用户在初始加载时需要下载的资源大小。