You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MutationObserver中用document.write致浏览器加载停滞的原因及方案问询

问题与解决方案

免责声明

我已了解为何应避免使用document.write以及document.write会清空页面的相关内容。

需求场景

需要在DOM加载阶段(document.readyState = 'loading'),在某一特定现有同步脚本之前,注入一个**同步(阻塞型)**脚本到DOM中。

可编辑HTML时的实现方案

若有权限编辑HTML代码,可通过如下方式实现:

<html>
  <head>
    <title>It's okay</title>
    <script>
      document.write(
        '<script src="https://www.googletagmanager.com/gtm.js?id=GTM-WZNT8QT" onload="console.log(1)"><'+'/script>'
      );
    </script>
    <script src="https://code.jquery.com/jquery-3.6.3.min.js" onload="console.log(2)"></script>
  </head>
  <body>
    <script>console.log('DOM is parsed')</script>
  </body>
</html>

该实现可正常加载两个JS文件,控制台依次输出1、2、DOM is parsed。

使用MutationObserver时的异常情况

但尝试通过MutationObserver实现(检测新脚本节点插入但尚未执行的时机)时:

<html>
  <head>
    <title>Why not loading?</title>
    <script>
      // 假设此JS由浏览器扩展在HTML解析前注入
      new MutationObserver((mutationList) => {
        for (const mutation of mutationList) {
          for (const node of mutation.addedNodes) {
            if (node.tagName !== 'SCRIPT' || !node.src.includes('jquery')) {
              continue;
            }
            document.write(
              '<script src="https://www.googletagmanager.com/gtm.js?id=GTM-WZNT8QT" onload="console.log(1)"><'+'/script>'
            );
          }
        }
      }).observe(document, { childList: true, subtree: true });
    </script>
    <script src="https://code.jquery.com/jquery-3.6.3.min.js" onload="console.log(2)"></script>
  </head>
  <body>
    <script>console.log('Now DOM is parsed!')</script>
  </body>
</html>

出现异常:浏览器持续处于加载状态,控制台从未输出2和Now DOM is parsed!;检查实际HTML代码,head中仅存在googletagmanager脚本;JavaScript线程未阻塞,但document.readyState始终停留在'loading'状态,页面功能完全失效。

始终加载中

问题

  1. 为何会出现此情况?如何解释?我原本预期两个脚本都会被执行。
  2. 在无法编辑HTML但可在HTML解析前注入JS(如浏览器扩展)的场景下,是否有方法在指定脚本执行前加载同步代码?

注:实际使用MutationObserver时,我至少可通过在回调中执行node.setAttribute('src', 'text/prevented')阻止jquery代码运行,但我想理解为何原方案无法生效。


解答

问题1:异常原因解释

HTML解析器遇到同步脚本时会暂停解析,先执行该脚本。而MutationObserver的回调是异步触发的——当监听器检测到jQuery脚本节点被插入时,HTML解析器已经进入了执行该脚本的准备流程。

此时在异步回调中调用document.write,浏览器会将其视为“结束当前文档解析并写入新内容”,而非同步脚本执行期间的“插入到解析流中”行为。这直接清空了原本的jQuery脚本和后续DOM内容,导致浏览器陷入无限等待解析完成的状态,readyState一直停留在loading,后续代码自然无法执行。

问题2:可行的替代方案

在无法编辑HTML但能提前注入JS的场景下,有两种可靠方式实现同步脚本前置加载:

  1. 重写document.createElement拦截脚本创建
    在HTML开始解析前,重写document.createElement方法,检测到目标脚本节点(如包含jquery的src)时,先同步加载所需脚本,再允许原脚本创建。示例代码:

    const originalCreateElement = document.createElement;
    document.createElement = function(tagName) {
      if (tagName.toLowerCase() === 'script') {
        const script = originalCreateElement.call(this, tagName);
        const originalSetAttribute = script.setAttribute;
        script.setAttribute = function(name, value) {
          if (name === 'src' && value.includes('jquery')) {
            // 在同步上下文写入前置脚本,避免文档覆盖
            document.write('<script src="https://www.googletagmanager.com/gtm.js?id=GTM-WZNT8QT" onload="console.log(1)"><'+'/script>');
          }
          originalSetAttribute.call(this, name, value);
        };
        return script;
      }
      return originalCreateElement.call(this, tagName);
    };
    

    这种方式在目标脚本节点创建的同步流程中注入前置脚本,不会触发文档覆盖。

  2. 浏览器扩展用webRequest API拦截请求
    如果是浏览器扩展场景,可通过webRequest API拦截目标脚本(如jQuery的URL)请求,先同步加载前置脚本,再允许目标脚本请求继续。这种网络层面的拦截能确保前置脚本在目标脚本执行前加载完成,且不影响DOM解析流程。


内容的提问来源于stack exchange,提问作者nikitaeverywhere

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 04:17:52