另外,不像HTML,CSSOM构造不是增量型的,它不能边读边被构造。原因是一个文件末尾的CSS规则,可能会修改文件最顶部的规则。所以,如果进行增量构造,那就会导致渲染树的多次渲染,因此CSSOM节点随时可能会发生变化。那将会极大的降低用户体验,用户就可能看到页面效果不停的变化。所以当所有CSS规则被处理后,CSSOM树会被更新,然后渲染树也会被更新,最后才在显示器上呈现。
CSS确实是渲染阻塞资源。一旦浏览器发送一个请求去外部stylesheet,渲染树的构造就停止了。因此关键渲染路径(Critical Redering Path)也会被卡住,什么都显示不了。尽管如此,DOM树的解析仍然继续。
![](http://imgq8.q578.com/ef/0613/999d2d17e68004db.jpg)
想一下浏览器可能已经使用老的CSSOM树就生成了渲染树,并且显示了一些东西在显示器上,然后又碰到了外部CSS文件,怎么办?这种情况很糟糕,一旦外部CSS文件下载完成,CSSOM树要被更新,渲染树也要被更新,所有那些已经被显示的元素要被新渲染树的内容所替代重新显示,这就会导致闪烁,是很糟糕的用户体验。
所以浏览器会等等stylesheet下载解析完成,这样CSSOM准备好,渲染树就可以准备好,渲染关键路径就不会被阻塞了。基于上述原因,通常建议所有外部CSS文件要尽早的被加载,最好是放在里。
另一种情况,外部JS文件已经被完全下载了,而外部CSS文件仍然在后台下载,这是浏览器会执行JS文件么?有什么危害么?
我们知道,CSSOM提供了JavaScript API来跟DOM元素的样式进行交互。比如,你可以读取更新元素的背景颜色el.style.backgourndColor 属性。这个与el关联的style属性暴露了CSSOM API。
当stylesheet在后台下载的时候,JavaScript仍然会被执行,因为我们的主线程没有被阻塞。如果我们的JavaScript程序访问DOM元素的CSS属性,它会拿到当前CSSOM上的值。但是一旦stylesheet下载完成并被解析后,这就会导致CSSOM被更新,我们刚才JavaScript拿到的值就过时了,因此,在下载CSS的时候去执行JS是不安全的,应该尽量避免。
根据HTML5规范,浏览器可以下载一个JavaScript文件,但不会立即执行,直到所有stylesheets先被处理了。当一个stylesheet阻塞了JavaScript执行,这叫做脚本阻塞CSS。
![](http://imgq8.q578.com/ef/0613/a47fe6a36a9696cf.jpg)
上面这个例子,script-blocking.html包含了一个link标签(这个是CSS),然后跟着一个script标签(这个是JavaScript文件),这里JavaScript文件下载特别快,没有任何延迟,但CSS文件花费了6秒去下载。所以尽管JavaScript文件加载完成,它仍然不会被浏览器立即执行。只有当CSS文件被处理完,我们才能看到JavaScript输出的Hello World。
文档的DOMContentLoaded事件
这个事件DOMContentLoaded(简称DCL)在浏览器已经完成了所有DOM树的构建而发出的。这里有很多因素会影响这个事件的发生。
如果我们的HTML没有任何脚本,DOM解析不会被阻塞,DCL就会在解析完整个HTML立即发生。如果我们有阻塞的脚本,DCL就会等所有阻塞脚本被下载执行后再发生。
另外,如果我们把CSS考虑进来,事情就变得复杂了。尽管没有外部脚本,DCL仍然要等所有stylesheets被加载。因为DCL就记录一个时间点,说明整个DOM树准备好了,但如果CSSOM没有准备好,DOM树并不能被安全的访问。所以大多数浏览器会等CSSOM也准备好才发出这个事件。