目录前言一、懒加载的概述二、懒加载的基本原理三、懒加载的实现方式1、图片懒加载2、组件懒加载React / Vue1、React 组件懒加载2、Vue 组件懒加载3、路由懒加载1、React Router 路由懒加载2、Vue Router 路由懒加载四、懒加载的最佳实践五、懒加载的挑战和注意事项六、懒加载的实战应用1、“懒加载分页” 来做长列表优化1、实现原理2、实现步骤3、实现示例React4、优化2、虚拟滚动技术来做长列表优化1、虚拟滚动的基本原理2、虚拟滚动的工作原理3、虚拟滚动的优点4、虚拟滚动的实现步骤5、虚拟滚动的实现示例React6、优化方法前言通常懒加载和预加载在项目中是都要实现的。预加载请看此文前端「页面预加载」一、懒加载的概述前端页面的“懒加载”Lazy Loading是一个性能优化技术。核心思想是只有在用户需要某个资源时才去加载它而不是在页面加载时就一次性加载所有内容。这样可以显著提高页面的加载速度减轻服务器负担同时提升用户体验。应用场景图片懒加载仅在图片出现在视口viewport内时加载。组件懒加载只有当需要渲染组件时才加载组件。路由懒加载在用户访问某个路由时才加载相关的页面组件。懒加载的好处加快页面加载速度减少初始加载的资源量提升首屏渲染速度。节省带宽只加载用户需要的部分避免不必要的数据传输。提升用户体验用户快速访问页面减少等待时间。节省资源避免加载用户当前不需要的资源特别是在移动设备上节省数据流量和电池。二、懒加载的基本原理懒加载的基本原理就是延迟加载即在页面或应用加载时不会立即加载所有的资源或内容而是等到用户真正需要时才去加载。这通常通过监听用户的行为比如滚动、点击等或者使用 JavaScript 来动态加载资源。三、懒加载的实现方式1、图片懒加载图片懒加载的经典实现方式是通过监听滚动事件判断图片是否进入视口然后再动态加载图片资源。当前可以使用 IntersectionObserver 来简化这一过程。例子imgclasslazyloaddata-srcimage.jpgaltExample Image// 使用 IntersectionObserver API 实现图片懒加载constimagesdocument.querySelectorAll(img.lazyload);constobservernewIntersectionObserver((entries,observer){entries.forEach(entry{if(entry.isIntersecting){constimgentry.target;img.srcimg.dataset.src;// 将>.classList.remove(lazyload);// 加载完后移除懒加载类observer.unobserve(img);// 停止观察这个图片}});},{rootMargin:200px,// 提前 200px 加载避免图片加载延迟});images.forEach(imgobserver.observe(img));这种方法保证了只有当图片进入视口时才开始加载图片减少了不必要的请求。2、组件懒加载React / Vue在框架中组件懒加载通常结合 动态导入 和 Suspense在 React 中或 Vue Router在 Vue 中来实现。1、React 组件懒加载React 组件懒加载 Suspense 分包importReact,{Suspense,lazy}fromreact;// 使用 React.lazy 实现懒加载constHomelazy(()import(./Home));functionApp(){return(divSuspense fallback{divLoading.../div}Home//Suspense/div);}在 React 中React.lazy()和Suspense结合使用可以实现懒加载组件。当 Home 组件需要渲染时才会加载它fallback 用于展示加载中的提示。2、Vue 组件懒加载constHome()import(./Home.vue);在 Vue 中使用动态import()实现懒加载Vue 会根据需要加载组件减少初始加载的内容。3、路由懒加载在单页面应用SPA中通常我们会将路由的页面组件做懒加载只有在用户访问某个路由时才加载相关的页面。1、React Router 路由懒加载React 路由懒加载 Suspense 分包importReact,{Suspense,lazy}fromreact;import{BrowserRouter as Router,Route,Switch}fromreact-router-dom;// 使用 React.lazy 进行路由懒加载constHomelazy(()import(./Home));constAboutlazy(()import(./About));functionApp(){return(RouterSuspense fallback{divLoading.../div}SwitchRoute path/homecomponent{Home}/Route path/aboutcomponent{About}//Switch/Suspense/Router);}2、Vue Router 路由懒加载constHome()import(/views/Home.vue);constAbout()import(/views/About.vue);constroutes[{path:/home,component:Home},{path:/about,component:About}];通过这种方式Vue 和 React 都能实现路由级别的懒加载减少了初次加载时需要加载的代码量。四、懒加载的最佳实践使用IntersectionObserver对于图片和其他资源的懒加载推荐使用 IntersectionObserver API它能够监听元素与视口的交集简化懒加载的实现。合理设置懒加载阈值对于图片等资源可以通过设置 rootMargin 提前加载这样避免用户滚动到图片时加载延迟。异步加载静态资源对于大文件和模块可以使用动态导入import()进行懒加载而不是一次性加载所有代码。异步加载 JavaScript 和 CSS除了组件路由和图片JavaScript 和 CSS 文件也可以按需加载通过 Webpack 等工具实现。优化加载顺序确保最重要、最常用的内容尽早加载次要内容可以懒加载。五、懒加载的挑战和注意事项SEO 问题懒加载的内容可能会影响页面的索引。对于搜索引擎爬虫懒加载的资源可能不会被加载到。因此需要确保这些资源对于 SEO 友好通常需要使用服务器端渲染SSR或静态渲染。加载顺序懒加载的资源顺序需要合理安排避免因加载过慢导致的用户体验问题。兼容性问题IntersectionObserver 和动态 import() 在一些老旧浏览器中的支持较差因此需要注意浏览器兼容性。六、懒加载的实战应用1、“懒加载分页” 来做长列表优化懒加载 分页 是优化长列表如数据表格、聊天记录、文章列表等加载性能的常用技术。具体思路用户每次只加载能看见的数据, 当滚动到底部时再去加载下一页的数据。1、实现原理懒加载当用户滚动到列表的底部时触发数据的加载请求。这通常通过监听 scroll 事件来判断用户是否接近列表的底部。结合 IntersectionObserver 或传统的滚动事件都可以实现懒加载。分页将数据按照一定数量分块每次加载一页。当用户滚动到底部时我们请求加载下一页的数据。通常会传递一个 page 参数来获取特定页的数据。2、实现步骤步骤 1初始化页面初始化时加载第一页的数据。通常你会展示一个加载中的提示例如divLoading.../div。步骤 2监听滚动事件懒加载当用户滚动列表到接近底部时触发加载更多数据。通过检测用户的滚动位置判断是否需要加载更多内容。步骤 3加载数据分页 懒加载每次触发懒加载时请求新的分页数据并将其追加到已有数据中展示给用户。3、实现示例React假设我们有一个长列表每次加载 20 条数据接着用户滚动到底部时自动加载下一页数据。HTML / React 组件实现importReact,{useState,useEffect,useCallback}fromreact;constListComponent(){const[items,setItems]useState([]);const[loading,setLoading]useState(false);const[page,setPage]useState(1);// 当前页数const[hasMore,setHasMore]useState(true);// 判断是否还有更多数据constfetchDatauseCallback(async(){if(loading||!hasMore)return;// 防止重复请求setLoading(true);// 模拟网络请求实际使用时应替换为真实的 API 请求constresponseawaitfetch(https://api.example.com/data?page${page}limit20);constdataawait response.json();if(data.length20){setHasMore(false);// 如果返回的数据少于 20表示没有更多数据}setItems((prevItems)[...prevItems,...data]);setLoading(false);},[loading,hasMore,page]);useEffect((){fetchData();// 初始化时加载第一页},[page,fetchData]);consthandleScroll(e){constbottome.target.scrollHeighte.target.scrollTope.target.clientHeight;if(bottom){setPage((prevPage)prevPage1);// 滚动到底部时加载下一页}};return(div style{{height:400px,overflowY:auto}}onScroll{handleScroll}ul{items.map((item,index)(li key{index}{item.name}/li))}/ul{loadingdivLoading.../div}{!hasMoredivNo more data/div}/div);};exportdefaultListComponent;说明状态管理items存储加载的数据。loading指示是否正在加载数据防止多次请求。page当前页码用于分页请求。hasMore指示是否还有更多数据可加载。fetchData模拟 API 请求加载当前页的数据。根据返回的数据量来判断是否还有更多数据。handleScroll监听滚动事件判断用户是否已经滚动到页面底部。如果滚动到达底部就增加 page 值触发加载下一页数据。UI部分列表以 ul 和 li 的形式展示数据。在加载过程中显示 Loading…当没有更多数据时显示 No more data。4、优化防抖和节流在滚动事件中频繁触发 API 请求可能会影响性能。可以使用防抖debounce或节流throttle来减少请求的频率。// 使用 lodash.debounce 或 lodash.throttle 实现防抖/节流consthandleScrolldebounce((e){constbottome.target.scrollHeighte.target.scrollTope.target.clientHeight;if(bottom){setPage((prevPage)prevPage1);}},200);// 防抖延时 200 毫秒使用 IntersectionObserver 替代滚动事件IntersectionObserver 可以用来检测元素是否进入视口比传统的滚动监听更加高效和灵活。useEffect((){constobservernewIntersectionObserver((entries){if(entries[0].isIntersecting!loadinghasMore){setPage((prevPage)prevPage1);}},{rootMargin:200px});consttargetdocument.getElementById(loadMore);observer.observe(target);return(){observer.disconnect();};},[loading,hasMore]);虚拟化技术对于非常长的列表数千条数据可以使用虚拟化技术如 React Virtualized 或 react-window只渲染当前可见部分的内容极大提升性能。2、虚拟滚动技术来做长列表优化虚拟滚动Virtual Scrolling 是一种优化长列表渲染性能的技术特别适用于列表项非常多例如数百或数千项的场景。其核心思想是 只渲染用户可见的部分数据而不是渲染整个列表。随着用户滚动只有当前视口范围内的内容才会被渲染滚动出视口的内容会被卸载极大地提高渲染效率和性能。核心思路每次只渲染可视区域的列表数,当滚动后动态的追加元素并通过顶部 padding 来撑起整个滚动内容。1、虚拟滚动的基本原理虚拟滚动的基本思想是根据当前滚动位置动态地计算需要渲染的数据并且随着滚动事件的触发动态加载、卸载数据项。这样列表的 DOM 节点数量不会随着数据量的增加而增加从而避免性能瓶颈。2、虚拟滚动的工作原理视口大小首先确定用户的可见区域视口即用户在屏幕上看到的列表区域。渲染策略根据可见区域的大小和每个列表项的高度计算出当前需要渲染的数据项范围。只有这些数据项被渲染在页面中。滚动控制在用户滚动时更新已渲染的数据项动态加载新的内容并卸载不在视口中的数据项。通常通过设置 scrollTop 来模拟页面滚动的位置避免全量渲染。3、虚拟滚动的优点节省性能只渲染当前可视区域的数据项避免一次性渲染整个列表提高渲染速度。减少内存使用渲染和卸载 DOM 元素时节省内存和资源防止浏览器出现卡顿。改善用户体验提高页面响应速度尤其在滚动大数据量时能提供平滑的滚动体验。4、虚拟滚动的实现步骤实现虚拟滚动通常包括以下几个步骤步骤 1确定容器大小和每项高度首先需要知道列表容器的高度或宽度以及每个列表项的高度或者使用动态计算每个项的高度。这些信息帮助我们计算出需要渲染的数据项范围。步骤 2计算可见区域和加载范围根据用户的滚动位置计算出当前需要渲染的元素。例如用户滚动到列表的第 50 项时我们需要显示第 50 到第 70 项之间的内容。步骤 3动态渲染和卸载随着滚动事件的触发动态加载新数据并移除超出视口的数据项。步骤 4模拟滚动位置为了保持用户体验虚拟滚动技术会在容器外部插入一个占位元素例如空的 div并调整该元素的高度来模拟滚动位置的变化。这样做可以保持列表的原始布局。5、虚拟滚动的实现示例React假设我们需要实现一个简单的虚拟滚动列表有 1000 个项目每个项目的高度为 30px。importReact,{useState,useEffect,useRef}fromreact;constVirtualScroll({data}){constcontainerRefuseRef(null);const[visibleItems,setVisibleItems]useState([]);constitemHeight30;// 每个列表项的高度constcontainerHeight300;// 容器高度constbuffer10;// 缓冲区用来预渲染列表consttotalHeightdata.length*itemHeight;// 列表总高度useEffect((){consthandleScroll(){constscrollTopcontainerRef.current.scrollTop;conststartIndexMath.max(0,Math.floor(scrollTop/itemHeight)-buffer);constendIndexMath.min(data.length-1,Math.ceil((scrollTopcontainerHeight)/itemHeight)buffer);setVisibleItems(data.slice(startIndex,endIndex1));};handleScroll();// 初始渲染containerRef.current.addEventListener(scroll,handleScroll);return(){containerRef.current.removeEventListener(scroll,handleScroll);};},[data]);return(div ref{containerRef}style{{height:containerHeight,overflowY:auto,position:relative,}}div style{{height:totalHeight,position:relative}}{visibleItems.map((item,index)(div key{index}style{{position:absolute,top:(indexdata.indexOf(visibleItems[0]))*itemHeight,height:itemHeight,width:100%,}}{item}/div))}/div/div);};// 示例数据constdataArray.from({length:1000},(_,index)Item ${index1});constApp(){returnVirtualScroll data{data}/;};exportdefaultApp;说明容器的高度 (containerHeight)我们定义了一个固定的容器高度例如 300px。项目的高度 (itemHeight)每个列表项的高度是 30px。滚动事件监听我们通过监听容器的 scroll 事件来计算当前应当显示哪些数据项。可见项目计算在每次滚动时我们根据滚动位置和容器高度来计算出当前应该显示的项目范围并通过 setVisibleItems 更新视图。占位元素我们为整个列表设置了一个 div并通过动态调整它的高度来模拟整个列表的总高度从而保证页面的布局不会被破坏。6、优化方法动态计算项的高度如果每个列表项的高度不相等可以使用 getBoundingClientRect 等方法动态计算每个项的高度。此时您需要动态更新容器的总高度并计算每个项目的实际高度。使用第三方库为了更高效和灵活地实现虚拟滚动可以使用一些现成的库如react-virtualized支持列表、表格、网格等组件的虚拟化。react-window是一个更加轻量级的虚拟滚动库适用于渲染大量元素的场景。