为什么脚本放在body标签的末尾?

31

我知道这个问题被问了很多次,但我没有找到答案。那么为什么建议将脚本包含在body标签的末尾以实现更好的渲染?

来自Udacity课程https://www.udacity.com/course/ud884 - 渲染开始于DOM和CSSOM准备就绪之后。JS是HTML解析阻塞并且任何脚本都要在CSSOM准备就绪之后开始运行。

所以如果我们有:

<html>
    <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>CRP</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <!-- content -->
        <script src="script.js"></script>
    </body>
</html>

CRP的意思是:

CSSOM ready > JS execute > DOM ready > Rendering

如果脚本位于 head 中:

<html>
    <head>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <title>CRP</title>
        <link rel="stylesheet" href="styles.css">
        <script src="script.js"></script>
    </head>
    <body>
        <!-- content -->
    </body>
</html>

CRP将保持不变:

CSSOM ready > JS execute > DOM ready > Rendering

这个问题仅涉及“同步”脚本(没有使用async/defer属性)。


https://dev59.com/NXRC5IYBdhLWcg3wAcbU - epascarello
1
渲染不是一次性完成的,而是逐步完成的。如果JS在body结束时被阻塞,那么在此之前的页面将被渲染(如果CSSOM已准备好)。 - ernesto
7个回答

27

历史上,脚本阻止其他资源更快地下载。将它们放在底部可以使样式、内容和媒体更快地下载,从而给人一种性能得到改善的印象

进一步阅读:asyncdefer 属性。


8
我注意到你说“历史上”。这是否仍然正确,或者新的浏览器是否已经解决了这个问题? - Chris Walter
1
@ChrisWalter 你应该研究一下asyncdefer属性,最好不要通过w3schools来学习。 - Sampson
W3Schools是我在Google上的第一个搜索结果,看起来很清晰明了 :) 我想补充一下你的回答,那些属性直到Internet Explorer 10才被支持,所以最好将JavaScript代码添加到HTML末尾。 - Chris Walter
@ChrisWalter Internet Explorer 4 支持 defer。在第10版中,它的实现得到了修订。 - Sampson
让我们在聊天中继续这个讨论 - Chris Walter
显示剩余4条评论

5
在我看来,这是一种过时的做法。最近更倾向于使用JavaScript将需要DOM存在的任何代码分离到“DOMContentLoaded”事件监听器中。这并不一定是全部逻辑;许多代码可以在没有完整DOM访问的情况下初始化。
确实,在只检索脚本文件而没有其他内容(例如图片)的小窗口期间会出现一小段时间。通过添加“async”属性可以跳过此小窗口,但即使没有它,我建议将脚本标签放置在head中,以便浏览器尽早了解加载它们,而不是将它们(和任何未来的JS-initiated请求)留到最后才加载。

很好,实际上并不是所有的JS都会对DOM对象进行残酷的操作。 - Roko C. Buljan

5
最好的做法是将JavaScript标签放在HTML的结束标签之前,而不是放在HTML的头部。原因是HTML从上到下加载,先加载头部,再加载body,最后加载body中的所有内容。如果我们把JavaScript链接放在头部,整个JavaScript文件将在加载任何HTML之前加载,这可能会导致一些问题。 1. 如果你的JavaScript代码在JavaScript文件加载后立即改变HTML,那么此时还没有可用的HTML元素,因此它似乎无法影响JavaScript代码,并且你可能会遇到错误。 2. 如果你有很多JavaScript代码,它会明显地减慢页面的加载速度,因为它会在加载任何HTML之前加载所有的JavaScript代码。当你把JavaScript链接放在HTML body的底部时,它给了HTML足够的时间来加载,在任何JavaScript加载之前,这可以防止错误,并加快网站响应时间。 还有一件事:虽然将Javascript放在HTML的末尾是最好的方式,但将Javascript放在HTML的头部并不总是会引起错误。在使用jQuery时,常见的做法是将所有的代码放在一个“document ready”函数中: $("document").ready(function(){ // your code here }); 这个函数基本上是说,在文档准备好或完全加载之前,不要运行内部的任何代码。这将防止任何错误,但仍可能会减慢HTML的加载时间,这就是为什么将脚本放在所有HTML之后仍然是最好的做法。

1
这似乎是引用自其他地方的内容。包含引用来源是适当的。 - Teepeemm

2

在脚本标签下方放置的图像将等待JS脚本加载后才会加载。通过将脚本标签放在底部,您可以首先加载图像,从而使页面看起来更快。


是的,但这取决于图像的数量。因为浏览器同时处理src请求直到某个数量。另外请注意,您始终可以为<script>标签使用async属性。 - Roko C. Buljan
HTTP/1.1规范建议浏览器每个主机名同时下载不超过两个组件。 @RokoC.Buljan - bcr

1
我认为这取决于您的网站或应用程序。一些Web应用程序基于JavaScript。那么,在页面底部包含它就没有意义了,而是立即加载它。如果JavaScript只是向某些基于内容的页面添加一些不太重要的功能,则最好在最后加载它。加载时间几乎相同,但用户会更早地看到重要部分(在页面完成加载之前)。
这不是关于整个站点加载得更快,而是给用户留下某个网站加载得更快的印象。
例如: 这就是为什么基于Ajax的网站可以给人以更快的印象。界面始终相同。只有一些内容部分会改变。

1

这是一个非常有用的链接。对于任何给定的网页,都会从.html文件创建文档对象模型。同时也会从.css文件创建CSS对象模型。

我们还知道JS文件也可以修改对象。当浏览器遇到


0

不确定这是否有帮助,但是从这个资源script-tag-in-web中可以看出,即使将内联脚本放在body标签的末尾,它仍然会阻塞渲染。

下面的内联脚本是第一个阻塞渲染的。浏览器在执行长时间的循环之前不会在屏幕上绘制任何内容。

    <!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
let word = 0
        for(let i =0; i<3045320332; i++){
            word += i;
        }
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = word + 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

但是下面的'index.js'不会阻塞初始渲染,屏幕将被绘制,一旦外部的'index.js'运行完成,span标签将被更新。

    <!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script type="text/javascript" src="./index.js">
    </script>
  </body>
</html>

index.js

let word = 0
    for(let i =0; i<3045320332; i++){
        word += i;
    }
  var span = document.getElementsByTagName('span')[0];
  span.textContent = 'interactive'; // change DOM text content
  span.style.display = 'inline'; // change CSSOM property
  // create a new element, style it, and append it to the DOM
  var loadTime = document.createElement('div');
  loadTime.textContent = word + 'You loaded this page on: ' + new Date();
  loadTime.style.color = 'blue';
  document.body.appendChild(loadTime);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接