理解Vue中的diff算法原理
关于真实的DOM
重绘和重排
** 重绘不一定需要重排,重排必然会导致重绘**
重排: 渲染树需要重新计算
- 页面渲染初始化
- 元素的尺寸改变(盒模型的几何属性,如: 外边距、内边距、边框宽度、宽、高)
- 添加、删除可见Dom
- 元素的位置改变
- 浏览器窗口尺寸的改变
- 获取某些属性时,如:
offsetTop,offsetLeft,offsetWidth,offsetHeight,scrollTop,scrollLeft
注意:
- 如果在body最前面插入一个元素,会导致整个文档的重新渲染。但是在最后插入一个元素,则不会影响到当前的元素。
重绘:
当页面中元素样式的改变并不影响它在文档流中的位置(如:color, background-color, visibility等),浏览器会将新样式赋予给元素并重新绘制。
优化策略:
- 将多次改变样式属性的操作合并成一次操作,尽量使用操作class
- 将多次重排的元素设置为absolute或fixed,脱离文档流,这样不会影响到其他元素. 如:有动画效果的元素
- 在内存中多次操作节点,完成后添加到文档中去。如:渲染列表,可以在内存中构建整个列表的html片段,在一次性的添加到文档中去,而不是循环添加每一行
- 由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其它元素的重排。如果要对一个元素进行复杂操作时,可以先隐藏它,操作完成之后再显示,这样只会在显示和隐藏的时候触发两次重排
- 在需要经常取的,引起浏览器重排的属性值时,要缓存到变量
- 图片载入的时候设置宽高
虚拟DOM
指的是用 JS 对象的形式,来模拟页面上的DOM嵌套关系.
// 虚拟DOM
createElement(
// { String | Object | Function }
// HTML标签名、组件选项对象、或者async函数,必填
'div',
// { object } 对应的数据对象,可选
{
attrs: {
id: 'app'
}
},
// { string | Array }
// 字节虚拟节点,也可以使用字符串来生成“文本虚拟节点” , 可选
[
'你好,虚拟DOM',
createElement("h1", "新闻头条"),
createElement(MyComponent, {
props: {
total: 100
}
})
]
)
模板转换成视图的整个过程
- Vue.js 通过编译将模板转换哼渲染函数(render),执行渲染函数就可以得到一个虚拟DOM
- 在对模型进行操作的时候,会操作对应的Dep中的Watcher对象,Watcher对象会调用对应的update来修改视图。这个过程主要是将新旧虚拟DOM进行差异对比。
简单点讲:在vue的实现上,Vue将模板编译成虚拟DOM渲染函数。结合Vue自定义的响应系统,在状态改变时,Vue能够智能地计算出重新渲染组件的最小代价并应用到DOM操作上。
流程:
模板 -> 渲染函数 -> 得到Vnode -> Vnode和oldVnode进行对比 -> 渲染成真实的DOM
渲染函数: 是用来生成虚拟DOM的。Vue推荐使用模板来构建我们的应用界面,在实现中Vue布局模板编译成渲染函数。
vnode虚拟节点: 它可以代表一个真实的DOM节点,通过createElement方法将vnode渲染成DOM节点;虚拟节点可以理解成节点描述对象,描述了应该怎样取创建真实的DOM节点
patch: 将vnode渲染成真实的DOM,patch过程是对比新旧虚拟节点之间有那些不同,然后根据对比结果找出需要更新的节点进行更新。实际作用是在现有DOM上进行打补丁的形式来实现更新视图的目的。
diff算法
diff过程的整体策略:同层比较,深度优先,就近复用
patch方法:用于 比较 新旧节点的不同,然后更新的函数
- 没有旧节点,说明是页面刚开始初始化的时候 ,此时,根本不需要对比,直接新建
- 新旧虚拟Dom树的根节点完全一样才会调用patchVnode方法进行打补丁
- 新旧虚拟Dom树的根节点不一样,直接创建新节点,删除旧节点
patchVnode方法:
- 如果是非文本节点或注释节点
- 如果都有子节点,并且不完全一直,则调用 updateChildren 进行diff
- 如果只有新节点有子节点,旧节点没有,不用比较,直接全部新建到父节点
- 如果新节点没有子节点,老节点有子节点,直接删除
- 如果老节点是文本节点,则情况内容
- 如果新旧文本节点不相等时,更新新节点的文本内容
子节点不一致时,调用updateChildren方法
- 头头相同
- 尾尾相同
- 旧头新尾
- 旧尾新头
- 如果都不匹配,则尝试查找具有相同key的节点
- 如果没有找到,说明是新节点,直接创建
- 如果找到了,比较两个具有相同的key的新节点是不是同一个节点
- 不设key,只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象进行对比
- 如果key相同,但节点不同,则创建一个新的节点
设置新旧节点的头尾指针,新旧头尾指针进行比较,循环向中间靠拢,根据情况调用patchVnode进行patch重复流程,调用
参考网站
https://juejin.cn/post/6881907432541552648#heading-1
https://www.infoq.cn/article/udlcpkh4iqb0cr5wgy7f