为什么使用document.write不能重载多个defer脚本?

新手上路,请多包涵
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>demo</title>
  </head>
  <body>
    <script>
      const handError = (srcEle, num = 1) => {
        console.log(srcEle.src, ' is error');
        const url = new URL(srcEle.src);
        const newUrl = 'test' + srcEle.src.split('/').pop();
        const newSrciptStr = `<script src=${newUrl} onerror="handError(this)" onload="handLoad(this)"><\/script>`;
        document.write(newSrciptStr);
        // const scriptObj = document.createElement('script');
        // scriptObj.src = newUrl;
        // scriptObj.setAttribute('onerror', 'handError(this)');
        // scriptObj.setAttribute('onload', 'handLoad(this)');
        // document.body.appendChild(scriptObj);
      };
      const handLoad = (srcEle) => {
        console.log(srcEle.src, ' is loaded');
      };
      window.addEventListener('DOMContentLoaded', () => {
        console.log('DOMContentLoad');
      });
    </script>
    <div>demo</div>
    <script defer src="./1.js" onerror="handError(this)" onload="handLoad(this)"></script>
    <script defer src="./2.js" onerror="handError(this)" onload="handLoad(this)"></script>
    <script defer src="./3.js" onerror="handError(this)" onload="handLoad(this)"></script>
  </body>
</html>

为什么使用doeument.write不能重载异步脚本2.js和3.js,而使用appendChild能全部重载。图3为注释write使用appendChild的结果,图4为删除defer属性后使用write能够正常重载。



删除defer使用write的结果
目前估计产生问题的原因在:defer属性的异步加载脚本与DOMContentLoaded以及document.write的这三者之间有某种连系,导致在第一次写入脚本后,覆盖了页面,并一直处于DOMContentLoaded前的状态。具体是什么原因导致的,希望大神们能解惑。

阅读 1.2k
avatarAI BotBETA

在解释为什么使用 document.write 不能重载多个带有 defer 属性的脚本,而使用 appendChild 可以成功重载所有脚本之前,我们需要理解几个关键概念和它们之间的相互作用。

1. defer 属性的作用

<script> 标签具有 defer 属性时,它告诉浏览器该脚本将延迟执行,直到文档完全解析和显示之后(即 DOMContentLoaded 事件触发之后),并且脚本将按照它们在文档中出现的顺序执行。

2. document.write 的行为

document.write 方法主要用于在文档加载和解析过程中向文档输出内容。然而,一旦文档加载完成(即 DOMContentLoaded 事件触发后),再调用 document.write 会导致页面被重写,这通常不是你想要的结果。在这种情况下,它会导致整个页面内容被新的 <script> 标签替换,从而中断当前脚本的执行和其他任何 defer 脚本的加载和执行。

3. 问题解释

在你的例子中,当第一个 defer 脚本(如 ./1.js)发生错误时,handError 函数被调用,它试图通过 document.write 动态添加一个新的 <script> 标签来“重载”脚本。然而,由于此时文档可能已经完成了加载(至少是 DOMContentLoaded 事件已经触发),document.write 的调用会重写整个页面,包括已经加载的 DOM 和后续将要加载的 defer 脚本(如 ./2.js./3.js)。因此,这些脚本不再加载或执行。

4. 为什么 appendChild 可以工作

使用 appendChild 方法动态添加 <script> 标签不会重写整个页面,而是将新的 <script> 元素添加到 DOM 中。这允许页面保持其现有内容不变,并且新的脚本标签可以像页面中的其他脚本一样正常加载和执行,即使它们是在 DOMContentLoaded 事件之后添加的。

结论

因此,当你需要在文档加载完成后动态添加脚本时,应该使用 appendChild 或其他 DOM 操作方法,而不是 document.write。这样可以避免重写整个页面并保留页面的当前状态,包括已经加载和将要加载的其他脚本。

1 个回答
✓ 已被采纳新手上路,请多包涵

异步加载的脚本会在docuemnt的readyState属性变成了interactive后执行,但此时文档流已经关闭了,在使用write进行写入就会默认执行document.open()打开新的文档流进行写入,所以会覆盖页面。并且在执行write后,没有调用document.close()关闭文档流,所以浏览器会一直处于解析状态,即DOMContentLoaded之前。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏