感觉自己复习的已经差不多了,只是缺少面试机会,然而昨天突然的京东的电话,貌似让我直接丢掉了一个进大厂的机会,死在了最基础的问题,说实话自己还是没有完全复习好,前两天还觉得如果给我几个面试机会,估计我就已经上岸了,在现在看来仅仅只是自我感觉良好而已,还是要好好的复习一下,把知识好好总结一下。
昨天死在的问题是DOM和CSSOM的渲染过程是并行的还是串行的,当时也是没太理解什么意思吧,张嘴就来了一个串行,其实自己也是知道一块加载的,当时也是考虑到DOM的加载中,遇到css的代码会进行css的加载吧,就感觉不太确定了,直到他告诉我是并行的后,感觉我整个人都不好了,也是感觉以后的面试问题也没有自信了,直到继承的实现方式我没说出来以后,感觉整个人都不好了,也是感觉它与我无缘了。
在面试结束的时候,面试官貌似也是听出来了,直接就是告诉我不要太沮丧,虽然有些问题没说出来但是都不是特别致命,对比之后才能出结果。面试官人很好,但是我也知道这次面试的结果已经确定了,也是好好的把不会的东西再研究一下,面试失败中成长吧。所以私下里也是把浏览器的渲染过程,串行和并行的概念,实现继承的方式去看了一下。稍后会都写成博客记录。
-----------------------------------------------------------------------------------------
进入今天的正题-----------------------看下边的,上边的没用吓扯的--------------------
------------------------------------------------------------------------------------------
浏览器渲染页面前需要先构建 DOM 和 CSSOM 树。因此,我们需要确保尽快将 HTML 和 CSS 都提供给浏览器。这也是我们为什么要在页面底部引入JavaScript代码的原因。或者说可以在头部引用,但是前提是加上async、defer,或window.onload。
首先呢是构建DOM树,刚开始获取到的是原始的字节,这时候呢,首先会进行一个转换的过程,就是说通过指定的字符编码(utf-8或者gbk2312之类的)准换成字符,这个时候呢,是字符串一样的东西,会进行一个令牌化的处理,把这些字符串转换成W3C标准下的各种令牌大概是这样的(“<html>”、“<body>”),这个时候还不是标签对象呢,需要发出的令牌转换成定义其属性和规则的“对象”,最后才是DOM树的构建,由于标签之间的嵌套关系,创建的对象链接在一个树数据结构内,这个结构也会捕获原始标记中定义的父项-子项关系(比如说body是HTML对象的子项,是DIV对象的父类一样)。
整个流程的最终输出是我们这个简单页面的文档对象模型 (DOM),浏览器对页面进行的所有进一步处理都会用到它。
下面说一下CSSOM树的构建,整体的过程呢其实和上边的流程特别的相似。CSS 字节转换成字符,接着转换成令牌和节点,最后链接到一个称为“CSS 对象模型”(CSSOM) 的树结构,有人可能就要懵逼了,css怎么会有树形结构呢,也是依赖于类名的嵌套关系吧,为页面上的任何对象计算最后一组样式时,浏览器都会先从适用于该节点的最通用规则开始(例如,如果该节点是 body 元素的子项,则应用所有 body 样式),然后通过应用更具体的规则(即规则“向下级联”)以递归方式优化计算的样式。就是说没有明显关系的,会直接连接在body上。但是这个时候还不是完整的CSSOM树,它只显示了我们决定在样式表中替换的样式。
通过DOM树和CSS规则树,浏览器就可以通过它两构建渲染树了。浏览器会先从DOM树的根节点开始遍历每个可见节点,然后对每个可见节点找到适配的CSS样式规则并应用。渲染树生成后,还是没有办法渲染到屏幕上,渲染到屏幕需要得到各个节点的位置信息,这就需要布局的处理了。
布局阶段会从渲染树的根节点开始遍历,由于渲染树的每个节点都是一个Render Object对象,包含宽高,位置,背景色等样式信息。所以浏览器就可以通过这些样式信息来确定每个节点对象在页面上的确切大小和位置,布局阶段的输出就是我们常说的盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。
在绘制阶段,浏览器会遍历渲染树,调用渲染器的paint()
方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。
再说一下渲染阻塞吧,就是说在渲染的过程中遇见JavaScript标签的时候,会先去执行js的内容,直至脚本完成执行,然后继续构建DOM。举个例子:
<p>1<p>
<script>
document.write('<p>3</p>')
</script>
<p>2<p>
这个时候页面会显示三个段落,内容分别是1、3、2。就是说先执行js代码再继续渲染DOM。所以JS要放在最后的另外一个原因就是,获取dom元素时获取不到,因为这个时候dom还没有构建出来呢。
我们都知道HTML默认是流式布局的,但CSS和JS会打破这种布局,改变DOM的外观样式以及大小和位置。因此我们就需要知道两个概念:replaint
和reflow
。
reflow(回流)
当浏览器发现布局发生了变化,这个时候就需要倒回去重新渲染,这个回退的过程叫reflow
。reflow
会从html
这个root frame
开始递归往下,依次计算所有的结点几何尺寸和位置,以确认是渲染树的一部分发生变化还是整个渲染树。reflow
几乎是无法避免的,因为只要用户进行交互操作,就势必会发生页面的一部分的重新渲染,且通常我们也无法预估浏览器到底会reflow
哪一部分的代码,因为他们会相互影响。
repaint(重绘)
repaint
则是当我们改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸和位置没有发生改变。
需要注意的是,display:none
会触发reflow
,而visibility: hidden
属性则并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着布局空间,它会被渲染成一个空框。所以visibility:hidden
只会触发repaint
,因为没有发生位置变化。
另外有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow
或repaint
一次,而是会把这样的操作积攒一批,然后做一次reflow
,这又叫异步reflow
或增量异步reflow
。但是在有些情况下,比如resize
窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行reflow
。
引起reflow
现代浏览器会对回流做优化,它会等到足够数量的变化发生,再做一次批处理回流。
-
页面第一次渲染(初始化)
-
DOM树变化(如:增删节点)
-
Render树变化(如:
padding
改变) -
浏览器窗口
resize
-
获取元素的某些属性
浏览器为了获得正确的值也会提前触发回流,这样就使得浏览器的优化失效了,这些属性包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height
、调用了getComputedStyle()
。
引起repaint
reflow
回流必定引起repaint
重绘,重绘可以单独触发。
背景色、颜色、字体改变(注意:字体大小发生变化时,会触发回流)
减少reflow、repaint触发次数
-
用
transform
做形变和位移可以减少reflow
-
避免逐个修改节点样式,尽量一次性修改
-
使用
DocumentFragment
将需要多次修改的DOM元素缓存,最后一次性append
到真实DOM中渲染 -
可以将需要多次修改的DOM元素设置
display:none
,操作完再显示。(因为隐藏元素不在render
树内,因此修改隐藏元素不会触发回流重绘) -
避免多次读取某些属性
-
通过绝对位移将复杂的节点元素脱离文档流,形成新的Render Layer,降低回流成本