借助 Performance API 监测动态注入脚本的性能开销

作者:袖梨 2026-06-19
Performance API可精准捕获动态注入脚本的完整耗时,需在注入前、插入后、加载完成、执行开始、执行结束五节点打标并用measure计算各段,再结合资源类型、模块模式与缓存状态归因分析。

可以直接用 Performance API 精准捕获动态注入脚本(如 document.createElement("script")import())的完整加载与执行耗时,关键不是“有没有开销”,而是“在哪段耗时、由谁主导”。需在创建、插入、加载完成、执行开始、执行结束五个节点打标,再结合资源类型(内联 vs 外链)、模块模式(ESM vs CommonJS)、缓存状态做归因分析。

在关键生命周期节点打标记

动态脚本性能不能只看 load 事件,要拆解为可归因的阶段:

  • 注入前:调用 performance.mark('script-inject-start'),记录 DOM 操作发起时刻
  • 插入 DOM 后:在 scriptElement.parentNode.insertBefore(...) 后立即打标 performance.mark('script-inserted')
  • 加载完成:监听 script.onloadimport()then 回调,打标 'script-loaded'
  • 执行开始:若脚本支持,可在其首行插入 performance.mark('script-exec-start')(需可控代码);否则用 PerformanceObserver 监听 longtask 或结合 setTimeout(0) 近似定位
  • 执行结束:在脚本末尾或模块导出后打标 'script-exec-end'

之后用 performance.measure() 计算各段:例如 measure('inject-to-insert', 'script-inject-start', 'script-inserted')measure('load-to-exec', 'script-loaded', 'script-exec-start')

区分注入方式与执行模式的影响

同样一段逻辑,不同注入路径性能差异显著:

  • 外链脚本:耗时主要分布在 DNS → TCP → SSL → 请求响应 → 解析 → 执行。用 performance.getEntriesByType('resource') 查找对应 name,可直接读取 duration(总耗时)、connectStart 等字段定位瓶颈
  • 内联脚本(eval 或 data URL):跳过网络,但解析和编译开销更明显,script-exec-startscript-exec-end 段会拉长,尤其含大量闭包或正则时
  • ESM 动态导入(import('./mod.js')):首次执行有模块解析开销,且受 type="module" 特性影响(如自动 defer、CSP 限制),script-loadedscript-exec-start 可能达几十毫秒
  • 未缓存 vs 强缓存:对比 transferSizedecodedBodySize,若两者接近且 duration 极小,说明命中内存缓存;若 transferSize === 0duration > 0,可能是协商缓存(304)带来的验证延迟

结合 PerformanceObserver 实时捕获不可控脚本

对第三方或无法修改的动态脚本(如广告 SDK),无法在内部打标,此时靠 PerformanceObserver 监听 resourcelongtask 更可靠:

  • 监听 resource 类型,过滤 initiatorType === 'script'name 匹配目标 URL,获取真实加载耗时
  • 监听 longtask,当某次任务持续 > 50ms 且紧随脚本 load 事件之后,大概率是该脚本执行导致,可关联时间戳做推测
  • 配合 performance.memory 观察 usedJSHeapSize 是否突增,判断是否因脚本引入大量对象引发 GC 延迟

避免误判与干扰因素

高耗时不等于脚本本身差,需交叉验证:

  • 主线程正处理渲染帧(如 requestAnimationFrame 中密集计算),会导致 script-exec-start 推迟,应检查 Performance 面板中是否存在长任务阻塞
  • 脚本中调用了同步 XMLHttpRequestlocalStorage,会直接卡住主线程,这段应出现在 exec-startexec-end 内,而非加载阶段
  • 重复注入同一脚本(未去重)会触发多次解析,但浏览器通常复用已编译的 code cache —— 此时 load 耗时低,但 exec 段仍可能波动,需看 V8 code cache 是否命中(DevTools → Application → Cache Storage 中查 script 字段)

相关文章

精彩推荐