题干与适用场景
一个电商详情页同时加载分析、客服、广告和 A/B 测试脚本。移动端真实用户的 p75 LCP 为 3.8 秒、INP 为 280 毫秒,第三方 JavaScript 传输约 1.2 MB,来自 14 个来源;桌面实验室测试却看起来正常。你需要在保留必要业务能力的同时,降低第三方代码对网络、主线程、隐私和页面可用性的影响。
先澄清四个边界:哪些脚本必须在首屏前运行,哪些只在用户操作后需要;脚本是否依赖彼此的执行顺序;用户同意前是否允许发送数据;第三方故障时页面是否必须继续完成购买。第三方脚本属于页面执行环境的一部分,不能只把它当作普通静态资源或交给营销团队后不再负责。
这道题适合前端、Web 平台和性能工程岗位。公开前端面试资料把 async、defer、脚本顺序和性能取舍列为常见基础题;web.dev、MDN 和 W3C 资料则能核验加载时机、长任务、CSP 和脚本来源控制。题目核心是治理与验证,不是背诵某个框架的组件名。
面试官考察点
普通回答会说“都加 async”或“把脚本放到页面底部”。强回答会先按用户价值和关键路径盘点脚本,再根据依赖关系选择 async、defer、模块、互动后加载或 iframe facade,并定义每个脚本的失败边界。
面试官重点看五项能力:能否区分下载时间与执行时间;能否解释 async 的无序执行和 defer 的顺序保证;能否把性能预算、同意管理、CSP/SRI 和供应链风险放在同一决策里;能否隔离第三方故障而不阻塞结账;能否用 RUM、长任务和分组实验证明改动有效。
只引用 Lighthouse 分数是不够的。实验室环境不能代表低端手机、慢网络、广告拦截器或第三方服务器抖动;还要看到真实用户分位数、脚本执行耗时和业务完成率。
回答前需要澄清的问题
- 首屏真的需要哪些脚本? 支付按钮或必要的风控可能是关键路径;客服、推荐和回放通常可以等到互动或空闲时。
- 脚本之间有依赖吗? 独立分析适合
async;依赖 DOM 或固定顺序的应用脚本通常用defer;依赖关系不明时不能并行。 - 用户同意前能收集什么? 先定义目的、地区和同意状态,再决定是否创建脚本、cookie、网络请求或匿名统计。
- 第三方能访问页面中的什么数据? 同源脚本可读取页面上下文;客服和广告若只需展示,应优先 iframe 或服务端事件,缩小权限。
- 失败时的用户体验是什么? 购买、登录和导航必须有第一方路径;第三方超时不能让主线程或关键按钮永久等待。
- 能否替换或自托管? 自托管减少 DNS 和供应商故障,但会承担更新、完整性、许可和缓存责任,不能无条件视为更安全。
30 秒回答框架
“我会先建立第三方脚本清单,记录业务价值、依赖、字节数、网络请求、主线程时间、数据目的和失败影响,然后把脚本分成关键、延后和可删除三类。独立脚本用 async,需要 DOM 或顺序的脚本用 defer,互动组件用 facade 或 iframe,绝不把所有脚本统一并行。用户同意前不创建非必要追踪,CSP 只允许经过审核的来源,固定资源再配合 SRI。每个供应商有字节、执行时间和错误预算;RUM 按设备和同意状态比较 LCP、INP、长任务、业务转化和脚本错误。先灰度删除或延后,再用故障注入和回滚证明页面仍可完成核心任务。”
分步骤深入解答
1. 建立脚本清单和关键路径
每个脚本记录供应商、版本、加载触发器、依赖、请求域名、传输字节、解析与执行时间、长任务、读取的数据、业务负责人和删除条件。把收益写成可验证结果,例如“识别结账失败来源”,不要写成“提升体验”。没有清晰目的、负责人或指标的脚本先进入删除候选。
再画首屏和第一次交互的依赖图。关键路径只保留渲染首屏、登录、购物车和支付真正需要的第一方代码;分析、聊天、推荐、热图和营销标签大多可以在首屏后或用户明确触发后加载。脚本在下载完成后仍会占用解析、编译和主线程执行时间,所以“异步下载”不等于“没有性能成本”。
2. 按依赖选择加载方式
经典脚本没有属性时会阻塞 HTML 解析。async 会并行下载,下载完成就执行,多个脚本之间没有顺序保证;适合彼此独立的分析或广告。defer 也会并行下载,但等文档解析完成后按出现顺序执行,适合依赖 DOM 或其他脚本的应用启动。模块脚本默认延后执行,但其依赖图仍要单独评估。
一个简化决策表:
| 方式 | 执行时机 | 依赖保证 | 适合场景 | 主要风险 |
|---|---|---|---|---|
| 普通经典脚本 | 下载后立即执行并阻塞解析 | 按文档顺序 | 极少数必须同步的引导代码 | 拖慢解析和首屏 |
async | 下载完成即执行 | 无顺序保证 | 独立分析、广告、简单 widget | 可能打断解析,依赖会竞态 |
defer | 解析完成后按顺序执行 | 保留顺序 | DOM 或应用启动依赖 | 仍会延迟 DOMContentLoaded |
| 互动后加载 | 用户触发或空闲后 | 由加载器控制 | 聊天、地图、视频、回放 | 首次打开有额外等待 |
| iframe facade | 先展示静态外壳,再隔离嵌入 | 跨文档边界 | 视频、支付外观、复杂 widget | 交互通信和无障碍更复杂 |
不要给互相依赖的脚本都加 async。如果 plugin.js 需要 vendor.js,应该用有序的 defer、模块依赖或一个明确的加载器,而不是依赖下载速度碰巧正确。
3. 延后、裁剪或替换第三方功能
对非关键功能使用用户互动、requestIdleCallback(有超时兜底)或首屏完成后的队列触发。聊天按钮可先显示第一方静态入口,点击后才创建 widget;视频先显示缩略图和播放按钮,点击后嵌入 iframe。这样减少首屏网络和主线程成本,也避免用户从未使用的功能占用资源。
删除重复的标签管理器、重复分析 SDK 和未使用的实验脚本。必要时要求供应商提供精简构建、按页面拆分、压缩和缓存策略。自托管可以减少第三方 DNS 或可用性风险,但要建立版本更新、SRI、许可证、缓存失效和回滚流程;它不会自动消除脚本执行成本。
4. 隐私、安全和故障边界
在同意状态明确前,不创建非必要的追踪脚本,也不要先加载再“禁用发送”。同意变化后,定义是否需要清理 cookie、停止队列和撤回后续请求。脚本来源通过 CSP script-src 和相关连接指令收敛;固定版本的外部资源可配合 SRI 校验内容完整性。动态脚本、供应商二次加载和 strict-dynamic 的组合要单独审计,不能只看第一层域名。
第三方代码与第一方页面同源执行时,权限边界很宽。需要展示而不需要读取页面数据的组件优先放入 iframe,并用 postMessage 传递最小字段。购买、登录、导航和错误提示保持第一方可用;第三方超时通过超时、熔断和降级占位结束,不得阻塞主流程。
5. 预算与监控
为每个供应商和页面类型设预算:传输字节、请求数、主线程执行时间、长任务数量、错误率和对 LCP/INP 的贡献。预算超出时进入评审或自动降级,而不是等全站指标恶化才处理。预算要按低端设备、慢网络和同意状态分层,因为平均值会掩盖长尾。
实验室用 DevTools Performance、Network、WebPageTest 和故障注入观察解析、执行、长任务和单点故障。线上用 RUM 记录脚本来源、资源时序、PerformanceObserver 的 long task、LCP、INP、CLS、转化和错误。比较改动前后的设备分层、浏览器、地区和页面模板,避免把流量结构变化误当优化效果。
6. 灰度、回滚和供应商变更
先在一个页面模板和小比例移动流量灰度。每个脚本改动带版本、来源、同意配置和回滚开关;供应商更新也要经过同一流程。若某个脚本超时或报错,默认跳过或降级,不等待无限重试。回滚只恢复上一份经过审核的清单,不能临时从供应商 URL 拉一个未知版本。
验证核心不变量:第三方失败时用户仍能浏览、加入购物车和完成购买;未经同意不会发出受限数据请求;脚本预算未超标;CSP 报告没有新来源;关键交互的 INP 没有回归。对每个供应商保留删除或替代的触发条件。
7. 可执行的验证场景
至少测试慢 DNS、第三方 5xx、下载到一半断网、脚本抛异常、主线程长任务、同意撤回、浏览器禁用第三方 cookie、广告拦截器和旧浏览器。测试不仅看页面是否显示,还要断言购买状态、错误恢复、键盘操作、屏幕阅读器公告和数据发送边界。
用对照组只改变一个加载策略,例如同步改为延后,保持页面内容和流量规则不变。验证 p75/p95 LCP、INP、长任务、资源字节、业务转化和供应商错误;若主指标改善但客服首次打开变慢,报告取舍并调整加载触发器,而不是只报一个漂亮分数。
高质量示范回答
“我不会先给 14 个脚本全部加 async。第一步是建立清单,记录每个脚本的目的、依赖、请求域名、字节、主线程时间、数据用途、负责人和失败影响,然后把它们分成关键、延后和删除候选。支付和登录保留第一方关键路径,分析可以独立异步,依赖 DOM 或顺序的脚本用 defer,聊天、地图和视频用互动后加载或 iframe facade。
用户同意前不创建非必要追踪脚本;CSP 只放行审核过的来源,固定资源再用 SRI。需要展示但不应读取页面的功能放入 iframe,购买和导航永远有第一方降级路径。每个供应商有字节、执行时间、长任务和错误预算。
我会先对移动端小流量灰度,实验室用 Performance 面板、网络限速和第三方故障注入,线上用 RUM 按设备、地区和同意状态观察 LCP、INP、CLS、long task、资源时序、转化和错误。每个改动都可回滚,供应商更新走同样的版本审核。如果第三方失败,页面仍能完成核心购买;如果未经同意仍发出请求,或护栏超预算,就停止扩展。”
常见错误
- 所有脚本都加
async→ 依赖脚本可能乱序,执行仍会打断解析 → 先画依赖图,独立脚本用async,有序依赖用defer或模块。 - 只把脚本放到 body 底部 → 下载和执行仍会抢占主线程,关键交互仍可能变慢 → 按功能延后、裁剪、分包并测量执行成本。
- 只看 Lighthouse → 实验室没有覆盖低端设备、慢网络和供应商抖动 → 用 RUM 分层比较真实分位数、长任务和业务指标。
- 同意后再禁用追踪 → 脚本已经执行并可能发送请求 → 在创建脚本和网络请求前就判断同意状态。
- 把自托管当成万能安全方案 → 更新、完整性、许可证和脚本执行成本仍需管理 → 配合版本审核、SRI、CSP、预算和回滚。
- 第三方故障时重试到成功 → 关键页面被供应商拖住,单点故障扩大 → 设置超时、降级占位和第一方核心路径。
追问及应对
追问 1:分析脚本必须尽早发送首屏事件,能否放关键路径?
先确认事件是否真的需要在首屏绘制前发送。若只需记录页面浏览,可在首屏后批量发送并允许丢弃;若合规或归因要求更早,使用独立 async、短超时和小型首方队列,不能让它阻塞渲染。对比事件延迟与 LCP/INP 的业务价值后再定预算。
追问 2:供应商只给一个动态 URL,如何做 SRI?
无法为内容不断变化的 URL 固定一个可靠哈希。可以要求版本化资源、建立自托管和签名发布流程,或把功能隔离到 iframe/服务端;同时用 CSP、供应商访问审计和变更监控降低风险。不能伪造一个哈希或把 unsafe-inline 当作替代方案。
追问 3:第三方脚本在 defer 后仍产生长任务,下一步做什么?
defer 只改变执行时机,不减少解析和执行工作。先按 Performance trace 找到具体脚本和任务,再要求供应商拆分、延后到互动、减少功能或改用 iframe/服务端事件。若必须运行,在空闲窗口分批执行并设置超时,同时用 RUM 验证长尾是否改善。
追问 4:营销团队要求一次性加入五个标签,如何回答?
要求每个标签给出目的、负责人、预期决策、同意类别、性能预算和删除条件。先检查是否重复采集或能由现有事件满足;必要时在沙盒页面测量,再小流量灰度。没有可验证价值或超过预算的标签不进入生产清单。