# 1. 从输入url到得到html的过程中,浏览器做的工作大致分为以下几步:

  • 加载:根据请求的URL进行域名解析,向服务器发起请求,接收文件(HTML、JS、CSS、图象等)。
  • 解析:对加载到的资源(HTML、JS、CSS等)进行语法解析,建议相应的内部数据结构(比如HTML的DOM树,JS的(对象)属性表,CSS的样式规则等等)
  • 渲染:构建渲染树,对各个元素进行位置计算、样式计算等等,然后根据渲染树对页面进行渲染(可以理解为“画”元素)

这几个过程不是完全孤立的,会有交叉,比如HTML加载后就会进行解析,然后拉取HTML中指定的CSS、JS等。

# 1.1. 浏览器渲染页面的过程:

渲染过程

  • 第一步,用 HTML 分析器,分析 HTML 元素,构建一颗 DOM 树(标记化和树构建)。
  • 第二步,用 CSS 分析器,分析CSS文件和元素上的inline样式,生成页面的样式表。
  • 第三步,将 DOM 树和样式表,关联起来,构建一颗Render树(这一过程又称为Attachment)。每个 DOM 节点都有attach方法,接受样式信息,返回一个render对象(又名renderer)。这些render对象最终会被构建成一颗Render树。
  • 第四步,有了Render树,浏览器开始布局,为每个Render树上的节点确定一个在显示屏上出现的精确坐标。
  • 第五步,Render树和节点显示坐标都有了,就调用每个节点paint方法,把它们绘制出来。
# 1.1.1. DOM树的构建是文档加载完成开始的?

构建DOM数是一个渐进过程,为达到更好用户体验,渲染引擎会尽快将内容显示在屏幕上。它不必等到整个HTML文档解析完毕之后才开始构建render数和布局。

# 1.1.2. Render树是DOM树和CSSOM树构建完毕才开始构建的吗?

这三个过程在实际进行的时候又不是完全独立,而是会有交叉。会造成一边加载,一遍解析,一遍渲染的工作现象。

# 1.1.3. CSS的解析是从右往左逆向解析的

从DOM树的下-上解析比上-下解析效率高),嵌套标签越多,解析越慢。

# 1.2. 渲染过程中遇到 JS 文件怎么处理?(浏览器解析过程)

JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复继续解析文档。

也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。

# 1.3. async 和 defer 的作用是什么?有什么区别?(浏览器解析过程)

  1. 脚本没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行。

  2. defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。 当整个 document 解析完毕后再执行脚本文件,在 DOMContentLoaded 事件触发之前完成。多个脚本按顺序执行。

  3. async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行,也就是说它的执行仍然会阻塞文档的解析,只是它的加载过程不会阻塞。多个脚本的执行顺序无法保证。

# 1.4. 什么是文档的预解析?(浏览器解析过程)

Webkit 和 Firefox 都做了这个优化,当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。

这种方式可以使资源并行加载从而使整体速度更快。

需要注意的是,预解析并不改变 DOM 树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。

# 1.5. CSS 如何阻塞文档解析?(浏览器解析过程)

  1. css加载不会阻塞DOM树的解析
  2. css加载会阻塞DOM树的渲染
  3. css加载会阻塞后面js语句的执行

# 1.6. 渲染页面时常见哪些不良现象?(浏览器渲染过程)

FOUC: 主要指的是样式闪烁的问题,由于浏览器渲染机制(比如firefox),在 CSS 加载之前,先呈现了 HTML,就会导致展示出无样式内容,然后样式突然呈现的现象。会出现这个问题的原因主要是 css 加载时间过长,或者 css 被放在了文档底部。

白屏:有些浏览器渲染机制(比如chrome)要先构建 DOM 树和 CSSOM 树,构建完成后再进行渲染,如果 CSS 部分放在 HTML 尾部,由于 CSS 未加载完成,浏览器迟迟未渲染,从而导致白屏;也可能是把 js 文件放在头部,脚本的加载会阻塞后面文档内容的解析,从而页面迟迟未渲染出来,出现白屏问题。

# 2. deferasync 的区别

difference between defer and async

  1. defer和async在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
  2. 它俩的差别在于脚本下载完之后何时执行,显然 defer 是最接近我们对于应用脚本加载和执行的要求的
  3. 关于defer,此图未尽之处在于它是按照加载顺序执行脚本的,这一点要善加利用
  4. async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行
  5. 仔细想想,async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics

参考资料:

  1. css加载会造成阻塞吗? (opens new window)
  2. defer 和 async 的区别 (opens new window)