页面加载太慢?一文搞懂 JS 和 CSS 阻塞机制!

大家好,我是 前端架构师 - 大卫

更多优质内容请关注微信公众号 @程序员大卫

初心为助前端人🚀,进阶路上共星辰✨,

您的点赞与关注❤️,是我笔耕不辍的灯💡。

背景

我们都知道,现代浏览器会并行下载各种资源(如 JS、CSS、图片等),但 JS 和 CSS 的加载与阻塞行为到底是什么?本文将通过实际案例、实验、配图,一次性讲清楚这些常见但又容易混淆的知识点。

先说结论

1. JS 的加载

  • 会阻塞 DOM 树的解析 (也就是说,只要 <script> 没有执行完,HTML 后面的内容不会被解析成 DOM)
  • 不会阻塞 DOM 树的渲染 (如果 DOM 已经解析出来,就能渲染,不用等 JS 下载完)
  • 不会阻塞 CSS 的解析 (浏览器会并行下载、解析 CSS 文件)

2. CSS 的加载

  • 不会阻塞 DOM 树的解析 (HTML 内容依然会被解析成 DOM)
  • 会阻塞 DOM 树的渲染 (页面直到 CSS 下载并解析完成后才会"真正"渲染出来,否则会出现白屏)
  • 会阻塞 JS 的运行 (在 <link> 后面的 JS 脚本,会等 CSS 下载完后再执行)

可以发现:CSS 和 JS 的"阻塞行为"刚好互补,相反。

准备工作

我们准备了一个稍大的 JS 文件和一个稍大的 CSS 文件。 在 Chrome DevTools 的 Network 面板,调整网速为 3G,勾选 Disable cache,以便模拟慢速网络环境,更容易观察资源的加载顺序与阻塞行为。

一、验证 JS 的加载阻塞行为

1. JS 的加载会阻塞 DOM 树的解析

html 复制代码
<!DOCTYPE html>
<html>
	<head>
		<title>JS 阻塞</title>
		<meta charset="UTF-8" />
		<script>
			setTimeout(() => {
				console.log(document.querySelector("h1"));
			}, 1000);
		</script>
		<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.js"></script>
	</head>
	<body>
		<h1>Hello</h1>
	</body>
</html>

打印结果:

csharp 复制代码
null

说明:

bootstrap.bundle.js 加载时,浏览器会暂停 DOM 解析,还没解析到 <body><h1>,所以 document.querySelector("h1") 得到的是 null。这也是为什么 JS 一般建议放在页面底部的原因。

2. JS 的加载不会阻塞 DOM 树的渲染

html 复制代码
<!DOCTYPE html>
<html>
	<head>
		<title>JS 阻塞</title>
		<meta charset="UTF-8" />
	</head>
	<body>
		<h1 style="color: red">Hello</h1>
		<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.js"></script>
	</body>
</html>

现象:

即使 JS 还在加载,DOM 已经被渲染出来了,页面的 "Hello" 文字已经显示为红色。

3. JS 的加载不会阻塞 CSS 的解析

html 复制代码
<!DOCTYPE html>
<html>
	<head>
		<title>JS 阻塞</title>
		<meta charset="UTF-8" />
		<link
			href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css"
			rel="stylesheet"
		/>
	</head>
	<body>
		<h1 class="text-warning">Hello</h1>
		<script src="./js/big.js"></script>
	</body>
</html>

现象:

即使 JS 还没加载完,Hello 文字已经渲染到页面上了(黄色字体)。

二、验证 CSS 的加载阻塞行为

1. CSS 的加载不会阻塞 DOM 树的解析

html 复制代码
<!DOCTYPE html>
<html>
	<head>
		<title>CSS </title>
		<meta charset="UTF-8" />
		<script>
			setTimeout(() => {
				console.log(document.querySelector("h1"));
			}, 100);
		</script>
		<link
			href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.css"
			rel="stylesheet"
		/>
	</head>
	<body>
		<h1 class="text-primary">Hello</h1>
	</body>
</html>

现象:

console.log 能成功拿到 h1 元素。说明 DOM 解析不会被 CSS 阻塞。

2. CSS 的加载会阻塞 DOM 树的渲染

说明: 虽然 DOM 结构已经被解析好了,但页面真正的"显示"会等 CSS 加载完才渲染。如果 CSS 很大或很慢,用户就会看到白屏,直到 CSS 下载完毕、解析完成,页面才正常显示(比如蓝色的 h1 才出现)。

3. CSS 的加载会阻塞 JS 的运行

html 复制代码
<!DOCTYPE html>
<html>
	<head>
		<title>CSS</title>
		<meta charset="UTF-8" />
		<script>
			console.time("CSS load");
		</script>
		<link
			href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.css"
			rel="stylesheet"
		/>
		<script>
			console.timeEnd("CSS load");
		</script>
	</head>
	<body>
		<h1 class="text-primary">Hello</h1>
	</body>
</html>

现象:

控制台打印的时间比较长(如 2759ms),JS被阻塞,必须等 CSS 完全加载解析后才会执行。

当然可以,下面这个总结会结合你列出的所有具体结论,并且明确对应到浏览器的设计行为与原理,让读者能从"为什么这样设计"更深入理解这些加载阻塞现象。

总结:浏览器资源加载阻塞机制背后的设计原理

浏览器对 JS 和 CSS 资源的加载阻塞行为,其实是围绕着页面渲染的正确性、交互的安全性和用户体验三者进行权衡与优化,核心原理体现在以下几点:

1. 关于 JS 的加载阻塞

  • 阻塞 DOM 树的解析,但不阻塞 DOM 渲染,也不阻塞 CSS 的解析

    • 设计目的 :JS 脚本经常需要读取和操作前面的 DOM 元素(比如绑定事件、修改内容),如果 DOM 还没解析好,JS 行为就会出错。因此浏览器选择在遇到 <script> 时暂停 DOM 解析,直到该 JS 加载并执行完毕再继续解析剩余的 HTML。这样可以确保脚本拿到的是完整、有效的 DOM。
    • 并行优化:JS 的加载和 CSS 的解析是并行的,这样不会让 CSS 的加载因为 JS 阻塞而延迟,从而加快页面样式的生效速度,提高整体加载效率。
    • 渲染保障:DOM 树一旦被解析出来就会渲染,即便 JS 还在加载,浏览器会优先让已解析的内容尽快显示给用户,提升"首屏体验"。

2. 关于 CSS 的加载阻塞

  • 不阻塞 DOM 解析,但阻塞 DOM 渲染和后续 JS 的执行

    • 设计目的:CSS 控制页面的外观和布局。浏览器在 CSS 未加载完成时,不会渲染对应的 DOM,是为了避免"无样式内容闪烁(FOUC)"和样式错乱。用户只会看到最终完整的页面样式,而不是先出现无样式内容再跳变成有样式内容。
    • 阻塞 JS 执行 :CSS 的加载还会阻塞 <link> 后面 JS 的运行。这是因为很多 JS 依赖于元素的最终样式(如获取宽高、计算位置),如果 CSS 没应用完毕,这些 JS 操作就会出现错误或不准确。因此,只有等 CSS 加载完,后续的 JS 才被执行,保证脚本逻辑的安全和正确性。
    • 并行优化:DOM 的解析依然是持续的(不会因为 CSS 加载而暂停),这样即便样式很大,DOM 结构可以先搭好,为后续渲染和交互做好准备。

综上,浏览器的这些加载阻塞行为,不是"性能问题",而是出于页面渲染一致性和脚本正确性的有意设计,既保障页面效果,又优化了加载体验。理解这些原理,才能在开发中写出结构更合理、加载更高效的页面。

相关推荐
杨超越luckly2 分钟前
HTML应用指南:利用GET请求获取全国小米之家门店位置信息
前端·arcgis·html·数据可视化·shp
海绵宝龙10 分钟前
axios封装对比
开发语言·前端·javascript
Data_Adventure10 分钟前
setDragImage
前端·javascript
南岸月明14 分钟前
七月复盘,i人副业自媒体成长笔记:从迷茫到觉醒的真实经历
前端
静水流深LY17 分钟前
Vue2学习-el与data的两种写法
前端·vue.js·学习
玲小珑23 分钟前
Next.js 教程系列(二十一)核心 Web Vitals 与性能调优
前端·next.js
YGY Webgis糕手之路38 分钟前
Cesium 快速入门(八)Primitive(图元)系统深度解析
前端·经验分享·笔记·vue·web
懋学的前端攻城狮39 分钟前
从 UI = f(state) 到 Fiber 架构:解构 React 设计哲学的“第一性原理”
前端·react.js·前端框架
三原43 分钟前
6年前端学习Java Spring boot 要怎么学?
java·前端·javascript
yourkin6661 小时前
Bean Post-Processor
java·开发语言·前端