HTML 页面加载和解析流程
-
用户输入网址(假设是个 html 页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回 html 文件。
-
浏览器开始载入 html 代码,发现<head>标签内有一个<link>标签引用外部 CSS 文件。
-
浏览器又发出 CSS 文件的请求,服务器返回这个 CSS 文件。
-
浏览器继续载入 html 中<body>部分的代码,并且 CSS 文件已经拿到手了,可以开始渲染页面了。
-
浏览器在代码中发现一个<img>标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码。
-
服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码。
-
浏览器发现了一个包含一行 Javascript 代码的<script>标签,赶快运行它。
-
Javascript 脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个<style>(style.display=”none”)。杯具啊,突然就少了这么一个元素,浏览器不得不重新渲染这部分代码。
-
终于等到了</html>的到来,浏览器泪流满面……
-
等等,还没完,用户点了一下界面中的”换肤”按钮,Javascript 让浏览器换了一下<link>标签的 CSS 路径。
-
浏览器召集了在座的各位<div><span><ul><li>们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的 CSS 文件,重新渲染页面。
总结:
-
总的来说就是按照 html 文档的顺序加载
-
还有就是最好将无论内部或是外部 JS 文件放到所有 html 内容之后,这样会令用户感觉页面加载速度变快了,否则如果将所有外部文件(包括 css 和 JS)引用都放到<head>中,意味着必须等到全部的 JS 代码都被下载解析和执行完毕后,才能开始呈现页面的内容(当浏览器遇到<body>),这样会导致呈现页面时出现明显的延迟,延迟期间的浏览器窗口将是一片空白。
HTML 页面加载和解析流程,版本二
js 放在 head 中会立即执行,阻塞后续的资源下载与执行。因为 js 有可能会修改 dom,如果不阻塞后续的资源下载,dom 的操作顺序不可控。
正常的网页加载流程是这样的:
-
浏览器一边下载 HTML 网页,一边开始解析
-
解析过程中,发现<script>标签
-
暂停解析,网页渲染的控制权转交给 JavaScript 引擎
-
如果<script>标签引用了外部脚本,就下载该脚本,否则就直接执行
-
执行完毕,控制权交还渲染引擎,恢复往下解析 HTML 网页
如果外部脚本加载时间很长(比如一直无法完成下载),就会造成网页长时间失去响应,浏览器就会呈现“假死”状态,这被称为“阻塞效应”。html 需要等 head 中所有的 js 和 css 加载完成后才会开始绘制,但是 html 不需要等待放在 body 最后的 js 下载执行就会开始绘制,因此将 js 放在 body 的最后面,可以避免资源阻塞,同时使静态的 html 页面迅速显示。将脚本文件都放在网页尾部加载,还有一个好处。在 DOM 结构生成之前就调用 DOM,JavaScript 会报错,如果脚本都在网页尾部加载,就不存在这个问题,因为这时 DOM 肯定已经生成了。
js、css 执行顺序
css
css 需要分块,首页的 css 独立,其余的 css 需要动态加载,因为 html 的绘制会被 css 阻塞,这样可以减少首次进入时的白屏时间。
js
js 的执行依赖前面的样式。即只有前面的样式全部下载完成后才会执行 js,但是此时外链 css 和外链 js 是并行下载的。(js 和 css 解析到后就会开始下载,不会等前面的 js/css 下载完才开始下载,所以也有可能后开始下载的较小的 js/css 先下载完成,不过运行顺序还是按照书写顺序,下载完如果前面还没运行也不会运行)
defer
外链的 js 如果含有 defer=”true”属性,将会并行加载 js,到页面全部加载完成后才会执行,会按顺序执行。
defer 属性的作用是,告诉浏览器,等到 DOM 加载完成后,再执行指定脚本。
-
浏览器开始解析 HTML 网页
-
解析过程中,发现带有 defer 属性的 script 标签
-
浏览器继续往下解析 HTML 网页,同时并行下载 script 标签中的外部脚本
-
浏览器完成解析 HTML 网页,此时再执行下载的脚本
对于内置而不是连接外部脚本的 script 标签,以及动态生成的 script 标签,defer 属性不起作用。
async
外链的 js 如果含有 async=”true”属性,将不会依赖于任何 js 和 css 的执行,此 js 下载完成后立刻执行,不保证按照书写的顺序执行。因为 async=”true”属性会告诉浏览器,js 不会修改 dom 和样式,故不必依赖其它的 js 和 css。
async 属性的作用是,使用另一个进程下载脚本,下载时不会阻塞渲染。
-
浏览器开始解析 HTML 网页
-
解析过程中,发现带有 async 属性的 script 标签
-
浏览器继续往下解析 HTML 网页,同时并行下载 script 标签中的外部脚本
-
脚本下载完成,浏览器暂停解析 HTML 网页,开始执行下载的脚本
-
脚本执行完毕,浏览器恢复解析 HTML 网页
async 属性可以保证脚本下载的同时,浏览器继续渲染。需要注意的是,一旦采用这个属性,就无法保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本。另外,使用 async 属性的脚本文件中,不应该使用 document.write 方法。
一般来说,如果脚本之间没有依赖关系,就使用 async 属性,如果脚本之间有依赖关系,就使用 defer 属性。如果同时使用 async 和 defer 属性,后者不起作用,浏览器行为由 async 属性决定。
林秀栋的技术博客