Snabbdom
是著名的虚拟DOM库,是diff算法的鼻祖,Vue源码借鉴了Snabbdom;
虚拟DOM
用javaScript对象描述DOM的层次结构.DOM中的一切属性都在虚拟DOM中有对应的属性.
diff是发生在虚拟DOM上的
新虚拟DOM和老虚拟DOM进行diff(精细化比较),算出应该如何最小量更新,最后反映到真正的DOM上
虚拟DOM是如何被渲染函数(h函数)产生的
-
h函数用来产生虚拟节点(vnode)
-
h函数:
第一个参数为标签节点
第二个参数为一个props对象,里面放html标签属性对象
第三个参数为标签文字
-
一个虚拟节点有哪些属性
{ children:undefined, //子元素 data:{ }, //属性,样式 elm:undefined, //真正DOM节点,如果为undefined则表示该节点还没有上树 key:undefined, //该节点的唯一标识 sel:'"div", //选择器 text:"我是一个盒子" //文字 }
而vnode函数的功能非常简单,就是把传入的5个参数组合成对象返回
vnode源码
export function vnode(sel, data, children, text, elm) { const key = data === undefined ? undefined : data.key; return { sel, data, children, text, elm, key }; }
-
h函数可以嵌套使用,从而得到虚拟DOM树
diff算法原理
(2个虚拟DOM如何进行差异化比较)
diff心得
- 最小量更新,key是节点的唯一标识,告诉diff算法,在更改前后 他们是否为同一个DOM节点
- 只能是同一个虚拟节点,才进行精细化比较.否则就暴力删除旧的DOM节点,插入新的DOM节点.
- 延伸问题:如何定义是否为同一虚拟节点?? 答:选择器相同且key相同
- 只进行同层级比较,不进行跨层级比较.即使是同一片虚拟节点,但是如果跨层级了,不会进行diff精细化比较,而是暴力删除旧DOM节点,重新插入新的DOM节点
diff处理新旧节点不是同一个节点时
patch(oldVnode,element) 源码; oldVnode 老节点; element新节点
return function patch (oldVnode: VNode | Element, vnode: VNode): VNode {
let i: number, elm: Node, parent: Node
const insertedVnodeQueue: VNodeQueue = []
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()
if (!isVnode(oldVnode)) {
oldVnode = emptyNodeAt(oldVnode)
}
//源码中sameVnode(oldVnode,vnode)方法 判断2个节点是否为同一个节点,返回一个布尔值
if (sameVnode(oldVnode, vnode)) {
//如果是 再调用patchVnode()精细化比较
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
//如果不是 暴力删除老节点,创建新节点重新插入
elm = oldVnode.elm!
parent = api.parentNode(elm) as Node
//创建新节点
createElm(vnode, insertedVnodeQueue)
if (parent !== null) {
api.insertBefore(parent, vnode.elm!, api.nextSibling(elm))
//删除老节点
removeVnodes(parent, [oldVnode], 0, 0)
}
}
//一些生命周期的钩子,暂不深入研究
for (i = 0; i < insertedVnodeQueue.length; ++i) {
insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i])
}
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
return vnode
}
如何定义同一个节点
源码中sameVnode(oldVnode,vnode)方法 判断2个节点是否为同一个节点,返回一个布尔值
判断旧节点的key要和新节点的key相同 且 旧节点的选择器要和新节点的选择器相同
function sameVnode (vnode1: VNode, vnode2: VNode): boolean {
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel
}
如果是 再调用patchVnode()精细化比较
如果不是 则暴力删除,重新创建新的直接点.所有新建的子节点都是需要递归出来的
(createElm源码递归创建新子节点部分)
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
const ch = children[i]
if (ch != null) {
api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue))
}
}
}
源码:createKeyToOldIdx() 缓存key
作用:就是把从开始的idx与结束的idx进行遍历存到一个map里,让map中的key等于下标 i 的项目
function createKeyToOldIdx (children: VNode[], beginIdx: number, endIdx: number): KeyToIndexMap {
const map: KeyToIndexMap = {}
for (let i = beginIdx; i <= endIdx; ++i) {
const key = children[i]?.key
if (key !== undefined) {
map[key] = i
}
}
流程图
经典的diff算法优化策略
四种命中查找:
- 新前与旧前 (此种情况发生,指针要往后移动)
- 新后与旧后 (此种情况发生,指针要往前移动)
- 旧后与新前 (此种情况发生涉及移动节点,那么新前指向的节点,移动到旧后节点之后)
- 新前与旧后 (此种情况发生涉及移动节点,那么新前指向的节点,移动到旧前节点之前)
命中一种就不再进行命中判断了.
如果都没有命中,就需要用循环来寻找了.
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!