共计 1377 个字符,预计需要花费 4 分钟才能阅读完成。
将 script 放在 body 中内容的最后的两点原因
- 避免 JavaScript 操作 DOM 失效
- 在解析 JavaScript 代码之前,将页面的内容完全呈现在浏览器中,用户会因为浏览器显示空白页面的时间缩短而感到打开页面的速度变快了
下面,对这两点原因进行说明。
原因分析:JavaScript 阻塞 HTML 解析
下面这张图来自 HTML 规范,图中清晰地描述了外部 JavaScript 对 HTML 解析的阻塞(内联脚本没有加载脚本这一环节,而是直接执行)。
图中,
parser:解析
fetch:获取资源
execution:执行
可以看到,解析 JavaScript 代码时(不管是嵌入式代码还是没有 defer
或async
属性的外部 JavaScript),页面的处理会暂时停止,此时浏览器窗口将是一片空白 。也就是说,<script>
标签会阻塞 HTML 的解析。
阻塞过程:
浏览器遇到 <script>
标签时,会唤醒 JavaScript 解释器,暂停 HTML 的解析,等到 CSSOM 构建完成(如果有的话),开始执行 JavaScript 脚本,JavaScript 执行完毕后继续解析 HTML。也就是说,浏览器会等待 JavaScript 资源下载完毕并执行完毕后才会继续解析 HTML。
这时,我们就会发现一个矛盾。JavaScript 是无法操作位于它下方的 DOM 的 ,因为此时 DOM 还没有构建出来。因此最好将<script>
放在 <body>
之后,也就是等到所有的 HTML 都解析完成之后,再进行 JavaScript 的相关操作。而 CSS 会阻塞 JavaScript 的执行,因此CSS 资源应优先于 JavaScript 资源被引入。
ps:由于 JavaScript 在操作 DOM 时,可能会引起浏览器的回流(reflow)或重绘(repaint),影响页面渲染性能,因此应该尽可能避免用 JavaScript 操作 DOM。
对于有 defer 属性的脚本,将 script 放在页面底部依然是最佳选择
下面这张图来自 Can I use…
从图中,我们可以知道:
- defer 属性只适用于外部脚本文件
- 当有多个延迟脚本时(也就是下面的情况),虽然 HTML5 规范要求脚本按照他们出现的先后顺序执行(也就是第一个延迟脚本先于第二个延迟脚本执行,而两个脚本都会先于 DOMContentLoaded 事件执行),但现实中,延迟脚本不一定会按照顺序执行,也不一定会在 DOMContentLoaded 事件触发前执行,因此最好只有一个延迟脚本
- 不支持 defer 属性的浏览器会忽略这个属性,像平常一样处理这个脚本(上图红色部分均不支持该属性,但可以看到支持它的浏览器已达到 97.82%,该属性还是很普遍的)。为了使该属性完美地发挥作用,把延迟脚本放在页面底部依然是最佳选择
<script type='text/javascript' src='example1.js' defer></script>
<script type='text/javascript' src='example2.js' defer></script>
JavaScript 代码编写建议
最后,根据以上描述,对于 javaScript 代码编写给出几点建议:
- 把全部 JavaScript 引入放到
body
元素中页面内容的后面 - 最好只包含一个延迟脚本
- 将延迟脚本也放在页面底部
- 将样式文件放在 JavaScript 之前(将 css 放在头部)