HTML的执行是浏览器内核来负责的,浏览器内核也就是浏览器的渲染进程 Renderer进程。
Renderer进程拥有多个线程:
GUI线程:
- 责渲染浏览器界面,解析HTML,CSS,构建DOM树和RenderObject树,布局和绘制等。
JS引擎线程:
- 也称为JS内核,负责处理Javascript脚本程序。
- GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
事件触发线程
定时触发器线程
异步http请求线程
任务队列轮询线程
这里的重点就是GUI渲染线程和JS引擎线程
HTML的解析过程是自上而下的
先解析head
标签中的代码
如果遇到<script>
,解析器(JS引擎线程)会立即解析并执行脚本,此时文档解析停止(GUI渲染线程停止)。如果是外部脚本,会等待脚本获取完毕再解析执行脚本。
当遇到引入样式表时,在Firefox中,样式表加载和解析过程中会禁止执行所有脚本,对于Webkit,仅当脚本尝试访问的样式属性可能受尚未加载的样式表影响时,它才会禁止该脚本。不会影响文档解析。
预解析
WebKit 和 Firefox 都进行了这项优化。在执行脚本时,其他线程会解析文档的其余部分,找出并加载需要通过网络加载的其他资源
预解析器不会修改 DOM 树,而是将这项工作交由主解析器处理;预解析器只会解析外部资源(例如外部脚本、样式表和图片)的引用。
解析完head
后,就会紧跟着解析body
此时浏览器开始渲染
所以如果在此前加载,执行的js存在堵塞,就会造成此期间浏览器完全空白。
defer
在script
标签中加上defer属性,脚本会被延迟到整个页面都解析完毕后再运行
相当于告诉浏览器立即下载,但延迟执行(只对外部脚本有效)
async
给脚本添加 async 属性的目的是告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到该异步脚本下载和执行后再加载其他脚本。正因为如此,异步脚本不应该在加载期间修改 DOM。(只对外部脚本有效)
<script>
标签打开defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。
defer与async的区别是:
defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;
async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。