核心是脚本执行依赖而非是否加async:外部js用defer安全操作dom;css用media="print"或preload+动态插入避免渲染阻塞;首屏iframe需fetch预取注入;video preload须用link且服务端优化moov位置。

防止资源异步引入阻塞 HTML 文档解析,核心不是“加不加 async”,而是“脚本执行时依赖什么”。不同资源类型(JS、CSS、iframe、video)的阻塞机制不同,需分类应对。
外部 JS 脚本:用 defer 代替 async 操作 DOM
async 脚本下载不阻塞解析,但一下载完就执行——此时 可能还没开始解析,document.getElementById('app') 必然返回 null。适合 analytics.js 这类完全独立的脚本。
真正安全操作 DOM 的方式是 defer:
- 下载异步,不中断 HTML 解析
- 执行严格排队,在整个 DOM 构建完成、
DOMContentLoaded触发前按 HTML 顺序执行 - 可放心调用
querySelector、addEventListener、初始化 Vue/React 根节点 - 注意:IE9 及以下不支持 defer,需用动态插入 +
document.write兜底
CSS 资源:避免渲染阻塞,用 media="print" 或 preload+动态插入
所有 <link rel="stylesheet"> 都会阻塞渲染,哪怕放在 前。浏览器必须等 CSSOM 构建完成才能绘制首屏。
非关键 CSS(如折叠面板、后台样式)应绕过阻塞:
- 写成
<link rel="stylesheet" href="non-critical.css" media="print">,初始不参与渲染 - 在
DOMContentLoaded后改media="all",触发加载 - 或用
<link rel="preload" as="style" href="non-critical.css">提前拉取,再动态插入<link rel="stylesheet"> - 禁用
@import,它永远阻塞且无法被 preload
iframe:首屏必需的不能靠 loading="lazy"
loading="lazy" 对首屏 iframe 完全无效——规范强制禁用。只要 iframe 初始位置在视口内(getBoundingClientRect().top ),浏览器就会立刻请求并阻塞主页面 DOM 解析。
正确做法分两类:
- 首屏必需 iframe(如地图、仪表盘):用
fetch预取 HTML 字符串,再通过iframe.contentDocument.write()注入(仅限同源) - 非首屏 iframe(如底部帮助文档):确保满足全部条件才用
loading="lazy"——静态声明、src 确定、缓存可控、明显在视口外、父容器无 transform/hidden、显式设宽高 - 更稳妥方案:彻底移除初始 HTML 中的 iframe,等
DOMContentLoaded后再用 JS 动态创建并赋值src
多媒体资源(video/audio):preload 必须用 link,不能写在标签上
<video preload="metadata"></video> 在多数浏览器中效果极差:iOS Safari 强制忽略,安卓 WebView 常跳过,服务端不支持 Range 请求时可能下完整文件。
真正有效的是:
- 在
中、<meta charset>和<title></title>之后、首个阻塞资源之前,写:<link rel="preload" href="video.mp4" as="video"> - 服务端 MP4 必须用
ffmpeg -c copy -movflags +faststart把 moov box 移到头部 - JS 层配合
IntersectionObserver,在视频进入视口时主动调video.load(),监听canplay防重复 - 动态插入的
<link rel="preload">无效,浏览器只在初始 parser 阶段识别
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











