虚拟DOM
虚拟DOM是一个对象
Diff算法
Diff算法是一种对比算法。对比两者是旧虚拟DOM和新虚拟DOM,对比出是哪个虚拟节点更改了,找出这个虚拟节点,并只更新这个虚拟节点所对应的真实节点,不用更新其他数据没发生改变的节点。
Diff算法的原理
Diff同层对比
新旧虚拟DOM对比时,Diff算法只会在同层级进行比较,不会跨级比较
Diff对比流程
当数据改变时,会触发setter,并且通过Dep.notify去通知所有订阅者watcher,订阅者们就会调用patch方法,给真实DOM打补丁,更新相应的视图。
patch方法
对比当前同层的虚拟节点是否为同一种类型的标签:
- 是,继续执行patchVnode方法进行深层比对
- 否,没必要比对,直接整个节点替换成新虚拟节点
function patch(oldVnode, newVnode) {
// 比较是否为一个类型的节点
if (sameVnode(oldVnode, newVnode)) {
// 是:继续进行深层比较
patchVnode(oldVnode, newVnode)
} else {
// 否
const oldEl = oldVnode.el // 旧虚拟节点的真实DOM节点
const parentEle = api.parentNode(oldEl) // 获取父节点
createEle(newVnode) // 创建新虚拟节点对应的真实DOM节点
if (parentEle !== null) {
api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素添加进父元素
api.removeChild(parentEle, oldVnode.el) // 移除以前的旧元素节点
// 设置null,释放内存
oldVnode = null
}
}
return newVnode
}
sameVnode方法
patch关键的一步就是sameVnode方法判断是否为同一类型节点
function sameVnode(oldVnode, newVnode) {
return (
oldVnode.key === newVnode.key && // key值是否一样
oldVnode.tagName === newVnode.tagName && // 标签名是否一样
oldVnode.isComment === newVnode.isComment && // 是否都为注释节点
isDef(oldVnode.data) === isDef(newVnode.data) && // 是否都定义了data
sameInputType(oldVnode, newVnode) // 当标签为input时,type必须是否相同
)
}
patchVnode方法
- 找到对应的
真实DOM,称为el - 判断
newVnode和oldVnode是否指向同一个对象,如果是,那么直接return - 如果他们都有文本节点并且不相等,那么将
el的文本节点设置为newVnode的文本节点。 - 如果
oldVnode有子节点而newVnode没有,则删除el的子节点 - 如果
oldVnode没有子节点而newVnode有,则将newVnode的子节点真实化之后添加到el - 如果两者都有子节点,则执行
updateChildren函数比较子节点,这一步很重要
function patchVnode(oldVnode, newVnode) {
const el = newVnode.el = oldVnode.el // 获取真实DOM对象
// 获取新旧虚拟节点的子节点数组
const oldCh = oldVnode.children, newCh = newVnode.children
// 如果新旧虚拟节点是同一个对象,则终止
if (oldVnode === newVnode) return
// 如果新旧虚拟节点是文本节点,且文本不一样
if (oldVnode.text !== null && newVnode.text !== null && oldVnode.text !== newVnode.text) {
// 则直接将真实DOM中文本更新为新虚拟节点的文本
api.setTextContent(el, newVnode.text)
} else {
// 否则
if (oldCh && newCh && oldCh !== newCh) {
// 新旧虚拟节点都有子节点,且子节点不一样
// 对比子节点,并更新
updateChildren(el, oldCh, newCh)
} else if (newCh) {
// 新虚拟节点有子节点,旧虚拟节点没有
// 创建新虚拟节点的子节点,并更新到真实DOM上去
createEle(newVnode)
} else if (oldCh) {
// 旧虚拟节点有子节点,新虚拟节点没有
//直接删除真实DOM里对应的子节点
api.removeChild(el)
}
}
}