理解Vue中的diff算法原理

关于真实的DOM

重绘和重排

** 重绘不一定需要重排,重排必然会导致重绘**
重排: 渲染树需要重新计算

  1. 页面渲染初始化
  2. 元素的尺寸改变(盒模型的几何属性,如: 外边距、内边距、边框宽度、宽、高)
  3. 添加、删除可见Dom
  4. 元素的位置改变
  5. 浏览器窗口尺寸的改变
  6. 获取某些属性时,如:offsetTop,offsetLeft,offsetWidth,offsetHeight,scrollTop,scrollLeft
    注意:
  • 如果在body最前面插入一个元素,会导致整个文档的重新渲染。但是在最后插入一个元素,则不会影响到当前的元素。

重绘:
当页面中元素样式的改变并不影响它在文档流中的位置(如:color, background-color, visibility等),浏览器会将新样式赋予给元素并重新绘制。

优化策略:

  1. 将多次改变样式属性的操作合并成一次操作,尽量使用操作class
  2. 将多次重排的元素设置为absolute或fixed,脱离文档流,这样不会影响到其他元素. 如:有动画效果的元素
  3. 在内存中多次操作节点,完成后添加到文档中去。如:渲染列表,可以在内存中构建整个列表的html片段,在一次性的添加到文档中去,而不是循环添加每一行
  4. 由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其它元素的重排。如果要对一个元素进行复杂操作时,可以先隐藏它,操作完成之后再显示,这样只会在显示和隐藏的时候触发两次重排
  5. 在需要经常取的,引起浏览器重排的属性值时,要缓存到变量
  6. 图片载入的时候设置宽高

虚拟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

posted @ 2021-03-29 19:13  yuxi2018  阅读(686)  评论(0编辑  收藏  举报