理解渲染过程(html Parser)
首先我们从浏览器的角度解释一下从输入 URL 到页面展示经历了些什么,以如下 html 文档举例
- <html>
- <head>
- <link rel="stylesheet" type="text/CSS" href="/style.css">
- <script type="text/javascript" src="/header.js"></script>
- </head>
- <body>
- <p>Text</p>
- <script type="text/javascript" src="/main.js"></script>
- </body>
- </html>
浏览器自上而下读取 html 文档(此过程叫 html parser),当发现 style.css 文件时,浏览器 parser 停下来去搞 css,等 style.css 下载并解析完毕,浏览器继续 parser。紧接着发现 header.js, 于是 html parser 又停了,浏览器下载并执行完 header.js,继续 parser。此时屏幕上还什么都没有。...parser,发现
,遂将 p 中文字展示了出来。紧接着又发现 main.js,浏览器又停下 parser,下载并执行完 main.js 才继续 parser,直到页面渲染完毕。
我们假设 header.js 中只有一行代码 console.log('header'), 但服务器响应很慢,要 10 秒才能把它返回给浏览器,浏览器执行这段代码需要 1ms,那在这 10s+1ms 内,页面将一直空白。浏览器执行 JS 的时间取决于代码质量和硬件,并不是前端工程师随便可以优化的,所以优化的重点在 JS 的下载时间。
非常简单,效果立竿见影,加快页面加载时间,多用于预解析 CDN 的地址的 DNS
- <!--在head标签中,越早越好-->
- <link rel="dns-prefetch" href="//example.com">
浏览器会在遇到如下 link 标签时,立刻开始下载 main.js(不阻塞 parser),并放在内存中,但不会执行其中的 JS 语句。
只有当遇到 script 标签加载的也是 main.js 的时候,浏览器才会直接将预先加载的 JS 执行掉。
- <link rel="preload" href="/main.js" as="script">
浏览器会在空闲的时候,下载 main.js, 并缓存到 disk。当有页面使用的时候,直接从 disk 缓存中读取。其实就是把决定是否和什么时间加载这个资源的决定权交给浏览器。
如果 prefetch 还没下载完之前,浏览器发现 script 标签也引用了同样的资源,浏览器会再次发起请求,这样会严重影响性能的,加载了两次,,所以不要在当前页面马上就要用的资源上用 prefetch,要用 preload。
- <link href="main.js" rel="prefetch">
上面我们的例子中,script 标签都是在没有多余属性的情况下执行的,只要下载过程结束,浏览器就会将 JS 执行掉。
defer 和 async 是 script 标签的两个属性,用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。
defer,async 与下载时机也有关,具体看这张图。
defer 的执行时间是在所有元素解析完成之后,DOMContentLoaded 事件触发之前。
async 的执行时间是在当前 JS 脚本下载完成后,所以多个 async script 是执行顺序是不固定的。所以 async 只能用于加载一些独立无依赖的代码,比如 Google Analysis 之类。
前面两节帮我们弄懂了 JS 的下载和执行时机,那什么样的页面才是完美符合现代浏览器的那?其实关键在于的 preload 和 prefetch!提前告知浏览器,我们的网站马上要用的是什么,以后可能要用的是什么,浏览器才能更快的渲染页面。下面是一段实例代码
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>Faster</title>
- <link rel="dns-prefetch" href="//cdn.com/">
- <link rel="preload" href="//js.cdn.com/currentPage-part1.js" as="script">
- <link rel="preload" href="//js.cdn.com/currentPage-part2.js" as="script">
- <link rel="preload" href="//js.cdn.com/currentPage-part3.js" as="script">
- <link rel="prefetch" href="//js.cdn.com/prefetch.js">
- </head>
- <body>
- <script type="text/javascript" src="//js.cdn.com/currentPage-part1.js" defer></script>
- <script type="text/javascript" src="//js.cdn.com/currentPage-part2.js" defer></script>
- <script type="text/javascript" src="//js.cdn.com/currentPage-part3.js" defer></script>
- </body>
- </html>
首先,Parser 在遇到 head 中 preload 时开始下载 JS,读到 script 标签的时候,如果已经下载完了,直接按顺序执行之。如果没下载完,会等到下载完再执行,这样就刚进入页面就开始非阻塞的下载 JS 了。
其次,页面会在空闲时,加载 prefetch 的 JS,如果之后页面发生跳转,跳转的目标页面引入了 prefetch.js,浏览器会直接从 disk 缓存中读取执行。
将 script 标签依然放在之前,并增加 defer 标签,确保老浏览器兼容,并在所有 DOM 元素解析完成之后执行其中的代码。
至此,完美的 HTML 结构出炉了。
CSS 的下载和解析一样会阻塞浏览器,造成白屏,CSS 中的字体文件更是影响首屏渲染关键因素之一,下一篇幅我会结合 preload 和 prefetch,带你一起优化 CSS,告诉你什么是最适合现代浏览器的 CSS 加载策略,期待的话,点个赞吧!
来源: https://sdk.cn/news/7920