为什么把 Script 标签放在 body 结束标签之后 html 结束标签之前?

新浪微博、谷歌等,为什么把Script标签放在body结束标签之后html结束标签之前? 而不是之前推荐的body结束标签之前? 问题关键:放在body结束标签之前与之后有什么差别?
关注者
284
被浏览
52,863

10 个回答

Google并没有把<script>插入在</body>之后,而只是没有写</body>和</html>闭合标签。 【这样做是符合标准的。不仅是html5标准,从第一个HTML正式标准HTML 2.0开始,这样做都是允许的。相反,在</body>之后插入其他元素,从HTML 2.0起就是不合标准的。】

新浪微博确实有在</body>之后输出<script>。

新浪微博所用的doctype是XHTML 1.0,但是其response的content type头并没有用XHTML mimetype,所以浏览器仍然会按HTML语法进行parsing。【顺便说一句,虽然新浪微博用了XHTML 1.0 DTD,但是其实际内容连well-formed也没有做到,是典型的挂羊头卖狗肉的XHTML。新浪的前端同学们应该检讨一下啊!当然了,腾讯微博也是用XHTML 1.0的DTD,并且也不是well-formed的,跟新浪微博是难兄难弟啊!如果说一定要较个高下,那企鹅还是略胜一筹——其网页出现第一个解析错误的行数更靠后一点。】

按照HTML5标准中的HTML语法规则,如果在</body>后再出现<script>或任何元素的开始标签,都是parse error,浏览器会忽略之前的</body>,即视作仍旧在body内。所以实际效果和写在</body>之前是没有区别的。

总之,这种写法虽然也能work,但是并没有带来任何额外好处,实际上出现这样的写法很可能是误解了“将script放在页面最末端”的教条。所以还是不要这样写为好。

【补充】
有同志说因为新浪微博使用bigpipe,所以“从直觉触发,为了避免浏览器出现未知异常,应该在首次吐出的页面框架中闭合body”。但是这个逻辑上说不通。因为在body以外写script也可能存在其他异常嘛。有什么理由能让开发者推断出后者会更安全呢?实际上在没有充分测试的前提下,如果要进行推断,那么可以推断出后者的风险更大。

第一,这是不合标准的行为,而且从有HTML标准以来都是不合标准的,因此浏览器实现不一致或者在这种情况下有bug的风险显然更大。

第二,虽然将<script>写在</body>之后,但最终的DOM树里,<script>元素还是会成为body的子节点,这一点很容易在firebug等调试器里验证。既然如此,如果将<script>写在</body>之前会有问题,你又如何保证写在之后(并在DOM里又变成了和写在之前一样的结构)就没有问题?

这里是浏览器加载一个有 <script> 标签的网站发生的事情:

  1. 拉取 HTML 页面 (e.g. index.html)
  2. 开始解析 HTML
  3. 解析到 <script> 标签之后准备获取 script 文件.
  4. 浏览器获取script文件。同时,html 解析中断并且阻断页面上其他html的解析。
  5. 一段时间后,script下载完成并且执行
  6. 继续解析HTML文档的其他部分(解析script之后的html代码)

第4步导致了不好的用户体验,直到script文件全部下载完成之前HTML都不能得到解析。

为什么会发生阻断事件?

任何script代码都能改变HTML的结构,通过document.write() 这种方式或者其他方式。 这就导致了HTML解析必须等待script全部被下载和执行完,HTML才能解析script标签之后余下的部分。

然而,大部分的Javascript开发者在加载文档过程中,不会通过script操作HTML的DOM结构。然而,他们必须等到script全部加载结束,才能看到页面。举个例子:

<!-- index.html -->
<html>
    <head>
        <title>My Page</title>
        <script type="text/javascript" src="my-script.js"></script>
    </head>
    <body>
        <div id="user-greeting">Welcome back, user</div>
    </body>
</html>

Javascript:

// my-script.js
document.addEventListener("DOMContentLoaded", function() { 
    // this function runs when the DOM is ready, i.e. when the document has been parsed
    document.getElementById("user-greeting").textContent = "Welcome back, Bart";
});

因为你的浏览器在下载并执行完my-script.js标签之前,并不知道my-script.js这个文件不会去修改DOM结构,导致HTML停止解析(在script下载并执行完之前)

之前推荐的方法(已过时):

之前解决这个问题的方法是把<script> 标签放到<body>标签之后 ,这确保了解析到</body>之前都不会被script终端。

这个方法是有问题的: 浏览器在整个文档解析完成之前都不能下载script文件,如果文档很大的话,解析完HTML,用户依然要等待script文件下载并执行完成之后,才能操作这个网站。(主要是串行,先解析HTML完,再下载并执行script,速度肯定没有并行块,那么怎么并行呢?我们假设能在解析HTML一开始,就开始下载script,并且不阻断HTML的解析,是不是就并行了呢)如果你的网站在2秒之内没有响应,用户就会跑掉;

现在推荐的解决方案:

现在浏览器script标签支持 asyncdefer 属性. 应用这些属性当script被下载时,浏览器更安全而且可以并行下载(下载script并不阻断HTML解析)。

async

<script type="text/javascript" src="path/to/script1.js" async></script>
<script type="text/javascript" src="path/to/script2.js" async></script>

async标记的Script异步执行下载,并执行。这意味着script下载时并不阻塞HTML的解析,并且下载结束script马上执行。
异步意味着,上述代码script2可能比script1先下载完并执行完。

根据 caniuse.com/#, 90% 的浏览器支持async属性.

defer

<script type="text/javascript" src="path/to/script1.js" defer></script>
<script type="text/javascript" src="path/to/script2.js" defer></script>

defer标签的script顺序执行。这种方式也不会阻断浏览器解析HTML。

跟 async不同, defer scripts在整个文档里的script都被下载完才顺序执行

根据 caniuse.com/#, 90% 的浏览器支持这个属性. 92% 至少部分支持此属性。

注意:在 IE <= 9 浏览器应用defer属性可能会导致script不会顺序执行。如果你想让低版本IE支持此属性,请看 this

结论

应用 async or defer这两个属性,拥抱未来。

原答案来自万能的: stackoverflow