singsong: 文章中 demo 猛戳这里吧
在讲解之前,先看一个问题。如下图所示,外部样式表是否会阻塞 HTML 解析(先不要看答案,可以自己思考和实验一下):
通过DevTools->network:
如上图所示,indexcss.css 并没有阻塞 HTML 解析,因为 DOMContentLoaded 时间线在 indexcss.css 之后。但如果在 indexcss.css 之后添加script 标签(不能为空),结果会一样?
如上图所示,在 indexcss.css 之后添加 script 标签(不能为空)后,此时 DOMContentLoaded 时间线位于 indexcss.css 之后。说明这里 indexcss.css 是阻塞 HTML 解析的。
Style sheets on the other hand have a different model. Conceptually it seems that since style sheets don't change the DOM tree, there is no reason to wait for them and stop the document parsing. There is an issue, though, of scripts asking for style information during the document parsing stage. If the style is not loaded and parsed yet, the script will get wrong answers and apparently this caused lots of problems. It seems to be an edge case but is quite common. Firefox blocks all scripts when there is a style sheet that is still being loaded and parsed. WebKit blocks scripts only when they try to access certain style properties that may be affected by unloaded style sheets.----Tali Garsiel
大概意思是:style-sheets 不会修改 DOM 树,没有理由为了解析 style-sheets 而阻塞文档解析(即 style-sheets 不会阻塞文档解析)。但如果在解析文档过程中有脚本需要访问样式信息时,为了保证访问样式信息的正确性。Firefox 会阻塞所有脚本直到 style-sheets 下载解析完为止。而 WebKit 只在访问的样式属性没有被加载解析时,才会阻塞脚本。
也即 style-sheet 不会直接阻塞文档解析,它只阻塞 script 的解析执行,才导致 style-sheet 间接阻塞文档解析。如果将 script 设置为非阻塞式的呢?可以通过为 script 标签设置 aysnc 特性来实现。可能你会疑问为什么不用 defer?
Both async and defer scripts begin to download immediately without pausing the parser and both support an optional onload handler to address the common need to perform initialization which depends on the script. The difference between async and defer centers around when the script is executed. Each async script executes at the first opportunity after it is finished downloading and before the window’s load event. This means it’s possible (and likely) that async scripts are not executed in the order in which they occur in the page. The defer scripts, on the other hand, are guaranteed to be executed in the order they occur in the page. That execution starts after parsing is completely finished, but before the document’s DOMContentLoaded event.
大概意思:async 和 defer 特性在脚本开始下载时,都不会阻塞文档解析。并且都支持 onload 事件回调处理,用于一些初始化工作。另外,两者对内联脚本都无效,脚本中不能调用document.write()
。而两者的不同之处:带有 async 特性的脚本会在脚本下载完后立即执行,且在 load 事件之前,所以不能确保脚本在文档中出现的顺序来执行。而带有defer特性的脚本会在文档解析完后按照在文档中出现的顺序来执行,且在 DOMContentLoaded 事件之前。
因此,这里设置 async 特性,而不设置 defer 特性。为了尽早地触发 DOMContentLoaded 事件,因为 defer 会延迟 DOMContentLoaded 事件触发。
为 script 标签添加 async 特性:
通过DevTools->network:
当然,这里也可以通过媒体查询 media让 style-sheet 异步加载:
通过DevTools->network:
- style-sheet 默认情况下是不会阻塞文档解析。
- style-sheet 只会阻塞 script 脚本解析执行。因为 script 脚本需要访问 style-sheet 样式信息,为了确保样式信息的正确性,因此 script 脚本需要等待 style-sheet 下载解析完。从而导致 style-sheet 间接地阻塞文档解析。
- style-sheet 可以通过媒体查询 media来实现异步加载。
- 为 script 设置 aysnc 特性来实现 script 异步加载,来加快文档解析。