前端性能优化里,“阻塞渲染”这四个字几乎每天都有人问,但真正把它彻底弄明白的人确实不多。我之前接手过一个内部管理系统项目,页面本身逻辑不复杂,可首屏就是慢,白屏时间能拉满到三秒多。后来一点点排查,才发现问题的根源不是接口慢,也不是图片大,而是几个放在<head>里的老式 JavaScript 文件把整个页面的渲染路径堵得死死的。那次排查之后,我对“脚本阻塞”这件事的理解完全上了一个台阶。
这篇文章不打算给你堆概念,我直接从我实际遇见的场景切入,把“阻塞渲染”是什么、JavaScript 是怎么把页面卡住的、以及我实际用过的几种解法一次说清楚。不论你是刚入门的前端新人,还是已经被性能问题折磨过几轮的开发者,这篇文章的实操内容都能直接拿过去用。
1. 阻塞渲染的本质:浏览器到底卡在哪一步
要理解阻塞渲染,必须先把浏览器拿到 HTML 之后的那几步“内心活动”搞清楚。这个过程经常被简化成一张图,但很多人看了图还是不懂,我换个方式讲。
1.1 从字节到像素的完整过程
浏览器收到 HTML 之后,不是一次性把整个页面画出来的。它有一套固定的流水线,大致是这样:
- 解析 HTML,挨个标签构建出 DOM 树结构。这个阶段叫做 Parse HTML。
- 遇到 CSS 就解析成 CSSOM(CSS 对象模型)。这个阶段叫做 Parse CSS。
- 把 DOM 和 CSSOM 合并成渲染树 Render Tree。注意,这一步会过滤掉
display: none之类的不可见节点。 - 计算每个可见节点的几何信息,比如宽高、位置、边距,这一步叫 Layout(或 Reflow)。
- 把计算好的节点逐个绘制成屏幕上的实际像素,这一步叫 Paint。更复杂的情况还会涉及合成(Composite)。
所以整个流程可以概括为两个独立但必须合流的内容:DOM 负责描述页面的“骨架”,CSSOM 负责描述页面的“皮肤”。两者全都准备好之后,浏览器才能进入 Layout 和 Paint 阶段。
我用一个比较好记的类比:DOM 像是盖房子时的施工图纸,CSSOM 像是装修方案。你不可能连施工图都还没看完,就开始刷墙;也不可能装修风格还没定,就急着摆家具。渲染这个动作,必须在施工图和装修方案都齐全之后才能开始动手。
1.2 关键节点:什么是“关键渲染路径”
从 HTML 拿到手,到用户眼睛真正看到画面,这一整条链路就叫关键渲染路径(Critical Rendering Path)。这条路径上任何一步被耽误,最终结果都是首屏白屏时间变长。
而 JavaScript 恰恰是这条路径上最“不守规矩”的环节。为什么?因为 JavaScript 有修改 DOM 和 CSSOM 的能力。浏览器在解析 HTML 的过程中一旦碰到<script>标签,它没法当作没看见继续往下走。它必须立刻停下来,先把脚本下载下来,然后执行完,因为脚本里可能就是document.getElementById(...).innerHTML = ...这类直接改动 DOM 结构的代码。如果不先执行完脚本,后面解析出来的 DOM 就可能和脚本期望的样子对不上,页面就乱套了。
提示:浏览器必须在拿到 JavaScript 的执行结果之后,才敢继续构建 DOM。这个强制的“停车等待”,就是阻塞的根源。
1.3 为什么 CSS 竟然也会“间接阻塞” JavaScript
这里有个经常被忽略的细节:不仅 JavaScript 会阻塞 DOM 解析,CSS 还会反过来阻塞 JavaScript 的执行。
原因是这样的:JavaScript 脚本在执行时,可能需要读取元素的样式,比如getComputedStyle()或者offsetWidth这些。如果 CSSOM 还没构建完,脚本读到的样式就是错误的。所以浏览器为了保证逻辑正确,在执行任何 JavaScript 之前,必须确保 CSSOM 已经构建完成。
换句话说,复杂的、体积庞大的 CSS 文件虽然没有直接阻塞 DOM 解析,但它会拖慢 JavaScript 的执行时机,从而间接拖慢了整个关键渲染路径。我后来在项目里对 CSS 体积做了瘦身,首屏时间实实在在地降了一截,这个连带关系是真实存在的。
1.4 重新定义“阻塞渲染”
到这里可以给阻塞渲染下一个精确的定义:由于外部资源(最常见的是同步 JavaScript,也包括部分 CSS 和字体文件)的下载或执行,导致浏览器无法持续推进 DOM 构建或渲染合成,用户看到可用页面的时间被不断推迟的现象,统称为阻塞渲染。
阻塞渲染的关键特征是“白屏”或者“局部不可交互”。用户看到的是空白页或者结构不完整的半成品页,而不是逐步加载出来的好体验。
2. JavaScript 到底是怎么把页面卡死的
技术原理讲完,得落到真实的代码场景里。我在各种项目里见过太多种 JavaScript 阻塞页面的姿势,下面这几种是最典型的,每一个我都实际踩过坑。
2.1 经典场景:<head>里的同步脚本
最常见的错误写法,就是把一个体积不小的脚本直接放在<head>里,比如这样:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>示例页面</title> <script src="https://cdn.example.com/lib/sdk-2.3.1.js"></script> </head> <body> <div id="app"></div> </body> </html>这个过程是怎么卡住的?我找一个具体的数字来说明。假设这个 sdk 脚本的大小是 400KB,不开启压缩的话,在普通的 4G 网络下下载就需要接近一秒钟。脚本下载的这一个过程,HTML 解析直接停摆;等脚本下载完,浏览器还要花时间去解析和执行它,假设它内部有大量初始化逻辑,执行又要花 200 毫秒。在脚本完成之前,<body>里的<div id="app">根本不会被解析到。
所以用户在这一秒多的时间里,看到的就是一个完整的白屏。这个问题在白屏问题排查里是最普遍的一种。
2.2 致命误区:以为内联脚本不需要优化
有同学会想,那我把脚本内联到 HTML 里,不走网络请求,是不是就解决了?
想法是对的,但问题没有消失,只是搬运了。比如下面这种写法:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>示例页面</title> <script> // 一个很重的初始化逻辑,这里只是示意 (function() { // 模拟大量数据处理 const list = new Array(10000).fill(0); for (const item of list) { // 做一些复杂计算 } // 模拟 DOM 重操作 window.__app__ = { loaded: true }; })(); </script> </head> <body> <div id="app"></div> </body> </html>内联脚本不需要下载,但它的执行依然发生在 HTML 解析的中途。也就是说,执行脚本时,<body>里的所有内容都还没被解析。如果这个内联脚本执行时间很长,效果和外部大脚本一模一样,都是白屏等待。
我自己就见过一个真实的例子:某公司官网把一段总数据量很大的配置逻辑直接塞进<head>里,这段脚本的执行时间有 800 毫秒。于是整个首屏就比正常情况多白了 800 毫秒,而且这个问题在开发环境几乎看不出来,因为开发机器性能太好,只有中低端手机用户受害最深。
2.3 容易被忽略的“多脚本排队”陷阱
还有一种情况,单个脚本都不大,但页面里有七八个,挨个排着:
<script src="https://cdn.example.com/a.js"></script> <script src="https://cdn.example.com/b.js"></script> <script src="https://cdn.example.com/c.js"></script> <script src="https://cdn.example.com/d.js"></script> <script src="https://cdn.example.com/e.js"></script>每个脚本 100KB,加起来不过 500KB。但浏览器对同一域名下的资源有连接数限制(通常同域名只能建立 6 个左右并发连接),这些脚本会排队下载。更重要的是,无论加载顺序如何,每个脚本的执行都是串行的,而且都是在阻塞 HTML 解析的状态下进行的。五个脚本就相当于五次不同的“停车等待”,每次都有下载加执行的时间,累计起来相当可观。
这种场景在老旧项目中尤其多。我分析过的某个第三方面板集成代码,光是各类 SDK 初始化脚本就放了八条,全部同步加载,导致首屏时间被活活拖长了两倍多。
2.4 其他你以为不太重要的资源也在捣乱
除了 JavaScript 之外,阻塞渲染还有一个隐身参与者,我前面提到过,就是 CSS。这里再展开一点:浏览器只有把 DOM 和 CSSOM 都准备完毕,才能生成渲染树。如果 CSS 文件体积超大,或者有几十个<link>标签串联依赖,CSSOM 的构建就会变得非常慢。而 JavaScript 的执行又必须要等 CSSOM 构建完成,等于 CSS 慢,JavaScript 也跟着慢,全链路一起堵。
另外,字体文件@font-face如果处理不好,也会造成文本闪动或不可见,这个虽然不是传统意义上的解析阻塞,但对用户体验的伤害是类似的。
3. 避阻塞的正解:async、defer 与工具链组合拳
说清楚问题之后,重点来了。我实际在项目里用过的方案有这么几类,按优先级从高到低列出来。
3.1 改变加载方式:从同步到异步的两种核心属性
现代浏览器给脚本提供了两个非常重要的属性:async和defer。它们都能让浏览器在下载脚本时不阻塞 HTML 解析,但语义完全不同,很多人在这里踩坑。
3.1.1defer:顺序保证,全程不阻塞
defer的中文意思是“延迟”。加上这个属性之后,浏览器会异步下载脚本,并且明确保证:脚本执行会在 HTML 解析完成之后,DOMContentLoaded 事件触发之前。多个defer脚本还会严格按照它们在文档里的顺序依次执行。
<script src="https://cdn.example.com/lib/sdk-2.3.1.js" defer></script>这个属性的典型适用场景是:你的脚本依赖 DOM 结构,不依赖提前执行,比如页面底部内容的初始化、第三方统计代码、视频播放器初始化等。我用defer处理过某地图 SDK 的初始化,效果非常明显,页面骨架先渲染出来了,地图脚本在后台慢慢加载。
3.1.2async:下载不阻塞,执行时机不可控
async的语义是“异步”,它让浏览器在下载脚本时也不阻塞 HTML 解析。但关键区别在于:脚本一旦下载完成,浏览器会立刻暂停 HTML 解析,先把这条脚本执行完,之后才继续解析。多个async脚本的执行顺序与它们在文档里的出现顺序无关,谁先下载完谁先执行。
<script src="https://cdn.example.com/sdk-a.js" async></script> <script src="https://cdn.example.com/sdk-b.js" async></script>上面这两个脚本的执行顺序是不确定的。如果sdk-b.js体积小、下载快,它可能先执行。如果你的业务逻辑依赖它们之间的先后顺序,用async就会出问题。
所以我的结论是:独立性强、无依赖关系、可允许执行时机自由浮动的脚本用async;依赖 DOM 并且多个脚本之间有顺序要求的,用defer。
3.1.3 对照表:同步、defer、async 三者的本质区别
| 属性 | 是否阻塞 HTML 解析 | 执行时机 | 执行顺序 | 适用场景 |
|---|---|---|---|---|
| 无属性(同步) | 是,下载和执行都阻塞 | 遇到即暂停解析,立即执行 | 按文档顺序 | 首屏必需且体积很小的脚本 |
async | 否,下载不阻塞,但执行时可能短暂阻塞 | 下载完成后立即执行 | 不保证,先下载完先执行 | 完全独立的第三方脚本、埋点统计 |
defer | 否,下载和执行都不阻塞 | HTML 解析完成后执行 | 按文档顺序保证 | 依赖 DOM 的页面脚本、有顺序依赖的脚本 |
我个人现在的默认选择是defer,因为它既保证了不阻塞,又保留了可以预期的执行顺序。async我基本只用在统计脚本这种完全不在乎时机的场景。
3.2 体积问题同步解决:压缩、拆包、Tree Shaking
属性改完了,有一个问题不能忽视:如果脚本本身太大,就算异步加载,下载时间依然很长,首屏上的某个区域还是会迟迟不能初始化。所以脚本体积的优化必须同步进行。
平时我在项目里做的体积优化有以下几项:
- 生产环境开启压缩:用构建工具自带的压缩能力把代码里的空行、注释、多余空格全部去掉,并且启用变量名压缩,一般能省下 30% 到 50% 的体积。
- 开启 Gzip 或 Brotli 压缩:在服务端配置响应头
Content-Encoding: br或Content-Encoding: gzip。JavaScript 这种文本资源的压缩率非常夸张,400KB 的脚本压缩后可能只剩 80KB 左右。我实测过,开启 Brotli 之后,某些三方的 SDK 加载速度几乎提升了一半以上。 - 代码拆分(Code Splitting):把首屏必需的代码和首屏不需要的代码分开打包。比如,只有用户点击弹窗才需要的组件,完全没必要随首屏一起加载。用动态
import()就可以做到按需加载。
// 只有当用户点击按钮时,才加载这个重模块 button.addEventListener('click', async () => { const module = await import('./heavy-module.js'); module.init(); });- Tree Shaking:这个主要依赖打包工具的静态分析能力。只要你在
import的时候只引入用到的成员,而不是把整个库全量引入,打包工具就能把没用的代码从产物里剔除干净。
3.3 现代工具链:type="module" 带来的原生异步能力
如果你在用现代前端框架开发,你会注意到构建产物里的<script>标签多数长这样:
<script type="module" src="/assets/index-7f3a9c.js"></script>type="module"的脚本默认就是defer语义,它不会阻塞 HTML 解析。模块中的import语句会被解析成额外的网络请求,这些请求也会并行加载。
但这里要注意一个实际体验:模块脚本的执行时机在 HTML 解析完之后,如果你的模块数量非常多,每个模块都要发一次请求,那么模块加载的总时间可能比单个打包文件还长。所以现代构建工具(比如 Vite 的生产模式)会在打包时把入口文件依赖的所有模块合并成少数几个文件,避免请求数过多。开发模式则保留模块化开发,牺牲一点首屏速度换来极快的热更新体验。
3.4 真正的“零阻塞”方案:把关键脚本放到</body>之前
还有一个老生常谈但我必须再强调的方法:把那些必须执行的、首屏必需的脚本,放到<body>的最底部,即所有 DOM 结构都解析完之后。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>示例页面</title> <style> /* 首屏样式 */ </style> </head> <body> <div id="app"> <!-- 页面内容 --> </div> <!-- 页面 DOM 结构已经全部解析完毕 --> <script src="https://cdn.example.com/lib/sdk-2.3.1.js"></script> </body> </html>这样做的好处是直白的逻辑合理:既然脚本依赖 DOM 存在,那就等 DOM 解析完再去执行它。在最老的项目里,这种手写方式依然有效,不需要工程化改造。当然,现代项目里我通常会配合defer属性,双保险。
4. 实操验证:我如何用 Performance 面板定位并解决一次白屏事故
理论说再多,不如一次完整的问题定位过程。下面这个案例取自一个真实项目的简化版,可以说是我处理阻塞渲染问题的一个标准流程。
4.1 事故现场:页面打开后两秒才看到内容
当时的页面结构大概是这样的:一个企业内部的运营活动页,包含活动主题、报名表单和一份活动规则说明。测试工程师在低端安卓机上反馈:打开页面后大约 2.5 秒,页面还是一片白屏。我的第一反应就是去看脚本加载方式。
页面源码里的<head>长这样:
<head> <meta charset="UTF-8"> <title>活动页面</title> <link rel="stylesheet" href="style-main.css"> <script src="https://cdn.example.com/sdk-track-1.0.1.js"></script> <script src="https://cdn.example.com/sdk-h5-sdk-2.0.0.js"></script> <script src="https://cdn.example.com/lib-bridge-3.2.0.js"></script> </head>三个脚本全是同步加载,而且相互之间没有明显的执行依赖。每一个脚本的执行逻辑里都写了一些全局变量的初始化,但事实上很多逻辑并没有在首屏就用到。
4.2 用 Performance 面板看真实卡死点
打开 Chrome 开发者工具,切到 Performance 面板,点击录制,然后刷新页面。等页面完全加载完之后停止录制,展开 Main 线程的火焰图,我能非常清晰地看到三处“下探”的深色区块,每一处对应了一个脚本的下载等待加执行过程。脚本执行的部分在火焰图上表现为一长条连续的 CPU 密集区块,旁边 HTML Parse 的进程被硬生生切断。
整个链路在时间线上的表现是这样:
- Parse HTML 开始,解析构建 DOM。
- 遇到第一个
<script>,Parse HTML 暂停。 - 下载脚本一,执行脚本一,耗时约 300ms。
- Parse HTML 恢复,但好景不长,又遇到脚本二。
- 下载脚本二,执行脚本二,耗时约 400ms。
- Parse HTML 恢复,遇到脚本三。
- 下载脚本三,执行脚本三,耗时约 500ms。
- Parse HTML 终于走到
<body>,开始解析真正的页面内容。 - 等待 CSSOM 也完整后,才生成渲染树,页面终于出现。
这个过程算下来,脚本阻塞导致的首屏时间被拉长了至少一秒钟以上。而页面真正显示出来的内容其实非常简单,一个标题段、一个表单按钮而已。
4.3 我的修改过程:一步一步消除阻塞
修改方向很明确,按优先级一步一步来:
第一步,删掉完全没有必要在首屏执行的业务启动逻辑。审查过后,发现三个 SDK 里有一个只在用户点击特定按钮后才可能被触发,我把它的加载方式从同步改为动态import()。
第二步,把剩余两个确实需要在首屏执行的脚本加上defer属性。原因是它们虽然要执行,但都依赖 DOM 结构(比如要绑定按钮的点击事件),而defer能保证所有 DOM 都解析完成后再执行,完美契合需求。
第三步,检查 CDN 是否开启压缩。确认之后,发现服务器没有返回 Brotli 压缩头,我联系对应配置的服务端同事,开启了 Brotli,三个脚本的传输体积平均下降了接近 40%。
第四步,把页面里一张首屏用不到的背景大图改成懒加载。这里不是 JavaScript 的问题,但同样在拖首屏时间。
改完之后再看 Performance 面板,时间线里 HTML Parse 的进程变得非常平滑,看不到中断的断层。首屏时间从 2.5 秒降到了 1.1 秒左右,在白屏区间的体感改善非常明显。
4.4 一个额外的排查工具:Long Tasks 接口
如果你不想依赖 Chrome DevTools 手动观测,还可以用 PerformanceObserver 来自动监听长任务:
const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { // 输出所有超过 50ms 的长任务 console.log('Long task detected:', entry); } }); observer.observe({ entryTypes: ['longtask'] });浏览器的主线程一旦出现任何超过 50ms 的任务,用户就会感受到明显卡顿。通过这个接口收集到的数据,可以帮助你量化页面里有哪些地方在执行阶段造成了阻塞。我把这个监听代码放在项目里的一个测试页上,定位到过好几个第三方库初始化时才暴露的 CPU 重任务。
5. 避坑与实战手册:从细节到极端场景
解决阻塞渲染的方案本身不复杂,但实际操作中总有一些容易忽略的细节,也有更极端的场景需要特殊处理。我把这些年积累的踩坑经验整理成了一份速查手册。
5.1 高频踩坑:乱用 async 导致初始化失效
我之前提到,async脚本的执行顺序是不确定的。这里举一个我真实遇到过的崩溃场景。
某项目的页面有两个脚本:
<script src="https://cdn.example.com/base.js" async></script> <script src="https://cdn.example.com/page-init.js" async></script>base.js里定义了一个全局工具函数window.utils.formatDate,page-init.js里在页面加载完后会调用这个函数来格式化日期。但加了async之后,这两个脚本的执行顺序变成了不可控,有时候page-init.js先执行,调用window.utils.formatDate时报错,整个页面初始化直接失败。
这种问题的唯一解法就是让 base.js 保持同步加载放在前面,或者把两者都改成defer,而不是async。我在项目里重构成defer之后,问题彻底消失。
提示:如果你不确定一个脚本和其他脚本之间是否存在隐藏的全局依赖关系,优先用
defer,不要用async。竞态问题排查起来远比想象中痛苦。
5.2 极端场景:脚本确实需要在解析早期执行怎么办
有些脚本是真的必须在 DOM 解析早期就执行,比如某些反爬虫验证逻辑、首屏性能监控脚本(它们要拦截最开始的请求阶段数据)。这种脚本不适合全部挪到</body>之后,也不适合用defer无限期延后。
遇到这种情况,我的处理思路是:
- 把最核心的、必须在早期执行的逻辑单独拆出来,尽量压缩体积。一个小的脚本也许只有几 KB,它的下载和执行时间都短到可以忽略,同步加载的代价完全可以接受。
- 剩余的逻辑拆成第二段,用
defer或动态加载处理。 - 在
<head>里给首屏关键脚本加上preload资源提示,让浏览器提前去下载它:
<link rel="preload" href="critical.js" as="script">preload的作用是给浏览器提前“通风报信”:这个资源很重要,你先下载着。这样即便是同步脚本,解析到它的时候也能直接执行而不用再等下载。
5.3 服务端的确认清单:你以为改了前端就万事大吉
很多时候前端已经把脚本属性调正确了,却发现加载速度依然没有提升。这时候要检查服务端,我列一份实际验证过的清单:
| 检查项 | 预期结果 | 常见问题 |
|---|---|---|
响应头是否带Cache-Control | cache-control: public, max-age=31536000, immutable | 没配置缓存会导致每次刷新都重新下载 |
| 是否启用 Brotli/Gzip | 应看到content-encoding: br或gzip | 未压缩时脚本传输体积偏大 |
| 是否启用 HTTP/2 | 应看到协议是h2 | HTTP/1.1 下同域名并发连接数只有 6 个左右 |
| CDN 节点是否覆盖 | 用户请求命中就近节点 | 回源慢会拖慢下载速度 |
这里特别要提一下 HTTP/2:它允许在同一个连接上并行传输多个请求,这就解决了 HTTP/1.1 下“脚本排队”的问题。如果你的站点还在用 HTTP/1.1,即使脚本全部defer,浏览器对同一个域名的连接限制依然会影响总下载时间。这是我在老站点迁移时体会很深的一点。
5.4 移动端的额外注意事项
移动端的渲染路径和桌面端大体相同,但有几个特殊性:
- 移动端 CPU 性能较弱,同一段脚本在桌面端可能只执行 100ms,在低端手机上可能膨胀到 400ms。所以脚本中如果有密集的循环、大量 DOM 操作,执行耗时的影响会被放大。
- 网络波动更大,脚本下载时间在弱网环境下可能成倍增长。数据预加载或者把关键逻辑内联化,是一种对抗弱网的手段。
- 如果页面里的 JavaScript 在首屏执行时间超过 50ms,那就应该被判定为一个长任务。这不仅会推迟首屏渲染,还可能造成点击延迟等交互问题。我平时会在移动端真机上跑一次 Performance 面板,专门确认长任务情况。
5.5 问题速查表:遇到白屏怎么一步步排查
最后整理一个直接可用的应急排查流程,按顺序执行能覆盖绝大多数阻塞渲染问题:
| 步骤 | 操作 | 判断依据 |
|---|---|---|
| 第一步 | 打开 DevTools Performance,录制刷新过程 | 看 Main 线程火焰图是否频繁出现中断 |
| 第二步 | 检查所有<script>标签是否有 async/defer | 无属性且位于<head>的同步脚本优先处理 |
| 第三步 | 检查脚本相互依赖关系 | 有依赖的用 defer,无依赖的可用 async |
| 第四步 | 检查 Network 面板里脚本的转输体积与耗时 | 体积大且压缩未开,需要开启 Brotli/Gzip |
| 第五步 | 检查是否有大量 CSS 阻塞 CSSOM | CSS 体积过大也会间接阻塞脚本执行 |
| 第六步 | 审查脚本内容,把不必要的初始化逻辑移到事件触发点 | 按需加载永远是最好的优化 |
| 第七步 | 用 PerformanceObserver 量长任务 | 确保任何任务都不超过 50ms |
这套流程我在多个项目里用下来,基本能覆盖 90% 以上的白屏问题。它不是教科书式的死流程,而是从实际问题倒推出来的排查顺序。
另外想提醒一句:性能优化之后一定要回归测试,特别是那些依赖初始化顺序的页面。我犯过的最大的错误是调整完一个脚本的加载顺序之后,另一个隐藏的全局变量没有被定义,导致整个业务逻辑断裂。优化不是简单改属性,要重新审视依赖关系。
我个人的习惯是,每次性能优化都会配合一份小的自查记录,把修改前的脚本列表、改动后的脚本列表,以及每个脚本的实际加载耗时都对比记录一遍。这样做的好处是,当未来再次出现疑似阻塞的问题时,可以直接翻记录确认哪些变量是已经验证过的安全项,排查速度会快很多。