在阻止后续请求方面,`document.write`和DOM API之间没有区别 - 为什么?

时间:2016-03-24 08:18:59

标签: javascript html dom browser

我正在阅读关于this articledocument.write,并声明:

  

有时脚本是由document.write添加的。不要使用它   方法,因为页面的其余部分将等待脚本加载和   执行。

     

如果远程服务器提起,则页面可能需要加载太多。

我并不真正理解在阻止方面使用document.write和DOM API之间的区别。我在Chrome中使用以下<head>块运行了一个简单的实验:

1)使用document.write

<head>
    <script src="index-1.js"></script>
    <script>
      var url = 'index-2.js'
      document.write('<script src="'+url+'"></scr'+'ipt>');
    </script>
    <link rel="stylesheet" href="index.css">
</head>

2)使用DOM API

<head>
    <script src="index-1.js"></script>
    <script>          
      var script = document.createElement('script');
      script.src = 'index-2.js';
      document.documentElement.firstChild.appendChild(script);
    </script>
    <link rel="stylesheet" href="index.css">
</head>

在这两种情况下,Chrome都会在加载index-1.js之前等待脚本index-2.js执行。这是图片:

enter image description here

所以我很困惑。我在做什么或者理解错了什么?

修改

如果我在链接标记

之后添加第三个脚本标记
<link rel="stylesheet" href="index.css">
<script src="index-3.js"></script>

然后我得到以下结果:

enter image description here

2 个答案:

答案 0 :(得分:2)

在第一个代码示例中,浏览器在运行任何其他脚本之前和显示页面之前等待index-2.js加载并执行。据说这是"blocking"负载。它会阻止解析器进一步前进,直到加载脚本。

在第二个代码示例(使用.appendChild()插入的脚本)中,浏览器在继续使用其他脚本之前或显示页面之前不会等待index-2.js加载或运行。这里index-2.js被异步加载,而页面中的其他内容继续被处理。据说这是"non-blocking"负载。它在加载时不阻止浏览器的进一步进展。一旦它完成在后台加载动态脚本,浏览器就可以随时自由地执行它(可能在此时没有别的事情可做)。当它实际执行时,它将因浏览器而异,并且不受规范的约束。

因此,时间与index-1.js无关。这是一个同步脚本标记。在两个例子中它都是阻塞的。浏览器不会处理该<script>标记。

区别在于您插入index-2.js后会发生什么。

执行顺序是由规范定义的,因此您必须专注于或衡量。当浏览器实际决定通过网络加载脚本时,它取决于浏览器,因此您无法使用加载条形图来查看执行情况。显然,它无法在解析器知道脚本之前开始加载它,并且必须在需要执行它之前加载它,但在此之内,浏览器可以使用自己的逻辑来决定何时加载它。因为每个浏览器都会限制它将尝试从一个主机下载的同时资源的数量,所以当它决定下载脚本时,可能很容易因浏览器而异。

并且,请记住,使用.appendChild()插入的非阻塞脚本可以在浏览器需要的任何时间运行。它几乎可以立即运行(如果浏览器在其缓存中有该脚本而没有其它任何事情可做,因为它正在等待其他资源),或者它可以在整个加载过程结束时运行它。当您使用.appendChild()插入它时,您指示浏览器不阻止当前解析和加载页面以运行此脚本,但只要浏览器具有该脚本的内容,它就可以在每次运行时运行它感觉就好了。

当您使用document.write()插入它时,您指示浏览器在当前脚本标记完成运行后立即运行它,这显然意味着它也必须立即加载。

如果你这样说:

<script src="index-3.js"></script>

<link>标记之前,然后您的时间轴会告诉整个故事,因为您会在第二个代码示例中看到index-3.js提前运行,因为它不会等待index-2.js加载并运行。实际上,如果您在console.log()index-1.jsindex-2.js中分别添加index-3.js个条目,那么您可以看到执行顺序的差异。

仅供参考,您还可以使用async代码中的defer<script>属性,使内联<script>代码无阻塞。这里有关于这些选项的更多讨论:load and execute order of scripts

答案 1 :(得分:1)

问题是,浏览器需要按照他们在页面中定义的顺序执行脚本。传统上浏览器执行此操作:

  • 阅读&amp;将HTML解析为脚本1
  • 下载脚本1
  • 解析脚本1
  • 执行脚本1
  • 阅读&amp;将HTML解析为脚本2
  • 下载脚本2
  • 解析脚本2
  • 执行脚本2
  • ...

由于效率非常低,浏览器开始执行speculative parsing。这些行动现在是

  • 阅读&amp;解析HTML直到最后
  • 下载&amp;解析所有资源(脚本,css,图像)
  • 执行script1
  • 执行script2
  • ...

document.write用于在浏览器解析HTML时写入HTML。当您使用它添加脚本时,浏览器需要立即执行您的脚本以保持正确的顺序。因此,它将开始下载,解析和执行您的脚本,而之前下载的资源只是在等待。

如果使用document.createElement方法,则在添加页面的其余部分后添加要执行的脚本。因此,浏览器将在继续呈现页面并在其后立即执行时下载脚本。