这是之前的一位朋友的酒桌之谈,他之前负责的一个电商项目,刚刚开发万,首页加载时间特别长,体验很差,所以就开始排查,发现是在首页一次性加载所有js导致的问题,这个问题在自己学习的时候并不明显,往往被大家称为编程习惯,甚至有的公司大佬进行项目框架层级的硬性规定或者封装,可以起到很好的作用,但是今天还是拿出来和大家分享一下:
场景描述
网站在首页一次性加载了所有模块的JavaScript资源,包括:
-
首屏轮播图
-
商品推荐列表
-
用户评论模块
-
页脚帮助中心
-
未可视区域的广告和营销组件
示例代码
// 问题代码示例 - 同步加载所有脚本 import Carousel from './components/Carousel'; // 首屏必要 import ProductList from './components/ProductList'; // 首屏必要 import Reviews from './components/Reviews'; // 需要滚动到中部才显示 import Ads from './components/Ads'; // 页尾才显示 import HelpCenter from './components/HelpCenter'; // 页脚折叠区域 function HomePage() {return (<><Carousel /><ProductList /><Reviews /><Ads /><HelpCenter /></>); }
问题
-
首屏加载时间过长
-
用户需要等待所有JS下载并执行完才能看到首屏内容
-
Lighthouse评分中"First Contentful Paint"指标很差
-
-
带宽浪费
-
加载了用户可能永远不会看到的资源(如未滚动的底部内容)
-
-
主线程阻塞
-
大量JS同步执行导致主线程长时间忙碌
-
用户交互(如点击搜索框)出现延迟
-
-
内存占用过高
-
初始化了所有组件,包括那些不需要立即显示的
-
排查思路
这个排查的思路很通用,大部份性能问题都可以从这里着手,如果是前端的新朋友,那么建议从开头编写完代码之后,多依着下面的排查思路看看自己的代码执行步骤,然后思考,会有意想不到的收获。
一、初步性能评估
-
打开Chrome DevTools (F12)
-
切换到 Network 面板
-
勾选 Disable cache (模拟首次访问)
-
选择 Fast 3G 网络限速(放大问题)
-
-
录制加载过程
-
刷新页面开始录制
-
观察所有资源的加载顺序和时间线
-
二、网络面板分析
-
JS加载问题识别
-
按类型筛选 JS 资源
-
检查:
-
是否首屏不需要的JS过早加载
-
是否有大体积JS阻塞渲染(红色长条)
-
JS文件是否并行加载
-
-
-
关键指标查看
markdown
复制
- Waterfall流中查找:* 蓝色竖线(DOMContentLoaded)* 红色竖线(Load) - 关注:* 首屏渲染完成时间* 主线程被JS执行阻塞的时间段
三、性能面板深度分析
-
Performance面板录制
-
点击 Record 后刷新页面
-
停止后查看时间线
-
-
关键区域检查
-
Main 线程活动:
-
长任务(超过50ms的黄色块)
-
JS编译与执行(紫色部分)
-
-
Network 网络请求:
-
JS加载与执行的关联关系
-
-
Timings 标记:
-
FP/FCP/FMP/LCP等关键时间点
-
-
-
典型问题模式识别
javascript
复制
// 问题特征示例时间线: [JS下载1][JS执行1][JS下载2][JS执行2]... // 优化后应有: [首屏JS下载][首屏渲染][惰性加载其他资源]
四、内存面板检查
-
Memory面板快照
-
取 Heap snapshot 比较:
-
页面刚加载时
-
滚动到底部后
-
-
-
内存问题识别
-
检查是否过早初始化了不必要组件
-
查看保留的DOM节点数量是否异常
-
五、Lighthouse自动化审计
-
生成报告
-
切换到 Lighthouse 面板
-
勾选 Performance 选项
-
点击 Generate report
-
-
关键指标关注
markdown
复制
- Opportunities中的建议:* "Defer offscreen images"* "Reduce unused JavaScript" - Diagnostics中的:* "Avoid enormous network payloads"* "JavaScript execution time"
六、具体问题定位步骤
-
确定关键渲染路径
-
在 Performance 录制中:
-
找到 FCP (First Contentful Paint)
-
分析之前的所有JS活动
-
-
-
识别非必要JS
javascript
复制
// 在Console执行: performance.getEntriesByType('resource').filter(r => r.initiatorType === 'script').sort((a,b) => a.startTime - b.startTime).map(r => ({name: r.name.split('/').pop(),start: r.startTime,duration: r.duration}))
-
加载顺序可视化
-
使用 Network 的时序图:
-
拖动选择首屏时间段(0-FCP)
-
右键 → "Save as HAR with content"
-
-
尝试解决
懒加载最直接的理解可以是按序加载,并不是一个很完整的操作,而是很多的细节拼凑或者是自己平常注意一种习惯吧。下面列出我自己习惯性的思路:
1. 动态导入 (Dynamic Import)
import { lazy, Suspense } from 'react'; const Reviews = lazy(() => import('./components/Reviews')); const Ads = lazy(() => import('./components/Ads')); const HelpCenter = lazy(() => import('./components/HelpCenter')); function HomePage() {return (<><Carousel /><ProductList /><Suspense fallback={<Spinner />}>{/* 当元素进入视口时加载 */}<LazyLoadComponent><Reviews /></LazyLoadComponent><LazyLoadComponent><Ads /></LazyLoadComponent><LazyLoadComponent><HelpCenter /></LazyLoadComponent></Suspense></>); }
2. Intersection Observer实现视口触发
// 自定义懒加载组件 const LazyLoadComponent = ({ children }) => {const ref = useRef();const [isVisible, setIsVisible] = useState(false); useEffect(() => {const observer = new IntersectionObserver(([entry]) => {if (entry.isIntersecting) {setIsVisible(true);observer.disconnect();}},{ threshold: 0.1 });observer.observe(ref.current);return () => observer.disconnect();}, []); return <div ref={ref}>{isVisible && children}</div>; };
3. 图片/iframe懒加载
<!-- 使用原生loading属性 --> <img src="product.jpg" loading="lazy" alt="Product"> <!-- 或使用Intersection Observer实现 --> <div class="lazy-image" data-src="product.jpg"></div>
性能影响数据(模拟)
指标 | 非惰性加载 | 惰性加载优化后 |
---|---|---|
首屏加载时间 | 4.2s | 1.8s |
总JS体积 | 1.8MB | 650KB(首屏) |
Lighthouse性能评分 | 48 | 82 |
首次输入延迟(FID) | 320ms | 110ms |
这些细节往往很小,但是很多,欢迎各位小伙伴一起讨论。