删掉冗余DOM节点可让首屏快120ms,因节点越多解析、渲染树构建和布局计算开销越大;实测移除3层嵌套div使首屏绘制从1.38s降至1.26s,但需判断节点是否承担语义或布局职责,避免盲目删除。

删掉冗余能快120ms,但别盲目删
DOM节点越多,解析、构建渲染树、布局计算开销越大;实测移除3层嵌套
,首屏绘制从1.38s降到1.26s。但这不是“删得越多越好”——关键看节点是否承担语义或布局职责。
- 能用
<header>、<nav>、<main>、<section>的地方,别堆<div>;语义缺失会让屏幕阅读器失效,JS查询路径也变长
- Flex/Grid布局下,90%的包裹层可直接删——容器本身就能承担布局职责,
display: flex或display: grid的父元素不需要额外<div>来“兜底”
- 在Chrome DevTools Elements面板里右键节点选“Break on > attribute modification”,观察哪些
<div>只起class容器作用,无样式、无JS绑定、无语义,这类就是高优先级清理对象
querySelectorAll('.card.active .content button')太慢,换种写法
深度嵌套CSS选择器在低端设备上匹配失败成本极高:浏览器得为每个button向上查多层父链。尤其DOM深度>6时,性能波动剧烈。
- 先定位容器再窄范围查找:用
document.querySelector('.card.active')拿到节点,再调用cardEl.querySelectorAll('button'),匹配路径大幅缩短
- 如果只需第一个按钮,用
cardEl.querySelector('button'),比全量查再取[0]少一次NodeList构建开销
- 避免
getElementsByClassName返回的HTMLCollection——它是实时集合,每次读.length都触发样式重计算
- 滚动监听里反复调用
document.querySelectorAll?立刻改用缓存引用或事件委托
内联critical CSS别塞满,否则TTFB翻倍
把全部CSS内联进<head>是最常见误操作。某电商首页这么做之后,HTML体积从18KB涨到64KB,TTFB延迟翻倍,CDN缓存命中率跌了37%。
- critical提取宽度要按真实设备分布调参:移动端占比超70%,就把viewport设为
width=375,height=667
- 用
penthouse替代critical默认方案——它支持自定义Puppeteer启动参数,能模拟真实用户滚动行为再抓样式
- 内联部分必须用
<style>标签包裹,且加media="print"属性,避免阻塞渲染;等页面加载完再用JS切换media值
loading="lazy"失效?不是bug,是插入时机问题
loading="lazy"在JS动态插入或SSR场景下极易失效——浏览器只对初始HTML中已存在的<img>生效。
立即学习“前端免费学习笔记(深入)”;
- JS通过
append()或框架v-for渲染的<img>,如果插入时已在视口内,浏览器跳过懒加载逻辑直接请求
- SSR输出的HTML中,
<img>标签必须已带loading="lazy",不能依赖客户端补加
- React/Vue项目中,避免在
useEffect或mounted里动态设置src——这会让浏览器当作“新资源”立即加载
- 轮播图、瀑布流等组件,手动用
IntersectionObserver控制更稳,loading="lazy"只是基础兜底
DOM树优化最易被忽略的点不在“怎么删”,而在“删完要不要测”。不同设备对节点数的敏感度差异极大:500+节点在千元安卓平板上就可能触发layout thrashing,但在桌面Chrome里毫无感知。上线前务必用真实低端机跑Lighthouse,看Render tree size和Layout shift两项指标。
DOM节点越多,解析、构建渲染树、布局计算开销越大;实测移除3层嵌套
- 能用
<header>、<nav>、<main>、<section>的地方,别堆<div>;语义缺失会让屏幕阅读器失效,JS查询路径也变长 - Flex/Grid布局下,90%的包裹层可直接删——容器本身就能承担布局职责,
display: flex或display: grid的父元素不需要额外<div>来“兜底” - 在Chrome DevTools Elements面板里右键节点选“Break on > attribute modification”,观察哪些
<div>只起class容器作用,无样式、无JS绑定、无语义,这类就是高优先级清理对象
querySelectorAll('.card.active .content button')太慢,换种写法
深度嵌套CSS选择器在低端设备上匹配失败成本极高:浏览器得为每个button向上查多层父链。尤其DOM深度>6时,性能波动剧烈。
- 先定位容器再窄范围查找:用
document.querySelector('.card.active')拿到节点,再调用cardEl.querySelectorAll('button'),匹配路径大幅缩短 - 如果只需第一个按钮,用
cardEl.querySelector('button'),比全量查再取[0]少一次NodeList构建开销 - 避免
getElementsByClassName返回的HTMLCollection——它是实时集合,每次读.length都触发样式重计算 - 滚动监听里反复调用
document.querySelectorAll?立刻改用缓存引用或事件委托
内联critical CSS别塞满,否则TTFB翻倍
把全部CSS内联进<head>是最常见误操作。某电商首页这么做之后,HTML体积从18KB涨到64KB,TTFB延迟翻倍,CDN缓存命中率跌了37%。
- critical提取宽度要按真实设备分布调参:移动端占比超70%,就把viewport设为
width=375,height=667 - 用
penthouse替代critical默认方案——它支持自定义Puppeteer启动参数,能模拟真实用户滚动行为再抓样式 - 内联部分必须用
<style>标签包裹,且加media="print"属性,避免阻塞渲染;等页面加载完再用JS切换media值
loading="lazy"失效?不是bug,是插入时机问题
loading="lazy"在JS动态插入或SSR场景下极易失效——浏览器只对初始HTML中已存在的<img>生效。
立即学习“前端免费学习笔记(深入)”;
- JS通过
append()或框架v-for渲染的<img>,如果插入时已在视口内,浏览器跳过懒加载逻辑直接请求 - SSR输出的HTML中,
<img>标签必须已带loading="lazy",不能依赖客户端补加 - React/Vue项目中,避免在
useEffect或mounted里动态设置src——这会让浏览器当作“新资源”立即加载 - 轮播图、瀑布流等组件,手动用
IntersectionObserver控制更稳,loading="lazy"只是基础兜底
layout thrashing,但在桌面Chrome里毫无感知。上线前务必用真实低端机跑Lighthouse,看Render tree size和Layout shift两项指标。



















