前文
之前我们聊到了diff是同级比较
那同级比较到底做了什么呢?
这里我采取最为普遍的Snabbdom源码来与大家一起进行探讨
原因有三
1.市面上的采取diff的源码其实大同小异,我们选取知识最多的一个源码分析
2.Vue采取了Snabbdom的基础上进行了修改,学Snabbdom=学Vue的diff
3.我他妈学的Snabbdom,没学过其他的
源码可以在https://github.com/snabbdom/snabbdom看
进入这个页面的上方可以通过
搜索源码。
关于如何定位可以查看开发文档,然后再翻阅源码。
这里我直接给出了,是patch
函数
在https://github.com/snabbdom/snabbdom/blob/79457fdf14ea5b8b8e7769119d139daf238e2e61/src/init.ts中
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);
}
if (sameVnode(oldVnode, vnode)) {
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;
};
}
我们来看看具体干了什么
patch的参数oldVnode: VNode | Element, vnode: VNode
是传入了旧虚拟dom和新虚拟dom
const insertedVnodeQueue: VNodeQueue = [];
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
声明了一个数组,我们暂时先不用管。
const cbs: ModuleHooks = {
create: [],
update: [],
remove: [],
destroy: [],
pre: [],
post: [],
};
查询cbs定义可以发现是一个钩子对象,触发了pre钩子。
if (!isVnode(oldVnode)) {
oldVnode = emptyNodeAt(oldVnode);
}
由于可以传入真实dom,所以这里判断了是否是虚拟dom,如果不是的话则转换为虚拟dom
if (sameVnode(oldVnode, vnode)) {
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);
}
}
这里使用了sameNode进行判断两个是否相等
function sameVnode(vnode1: VNode, vnode2: VNode): boolean {
const isSameKey = vnode1.key === vnode2.key;
const isSameIs = vnode1.data?.is === vnode2.data?.is;
const isSameSel = vnode1.sel === vnode2.sel;
return isSameSel && isSameKey && isSameIs;
}
sameVnode传入了两个参数
通过key,data,sel等结合判断了是否是相同的虚拟dom(一新一旧)
如果是的话,则调用
patchVnode(oldVnode, vnode, insertedVnodeQueue);
发起更新,同时传入了insertedVnodeQueue数组。
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);
}
如果不是的话,获取旧虚拟dom的elm属性(elm是真实dom)
并获取真实dom的父对象。
并根据新虚拟dom创建一个真实dom,挂载到新虚拟dom的elm节点上
判断旧dom的父对象是否为null
如果不为null
则将新虚拟dom的真实dom插入到该对象的下一个兄弟对象的前面,也就是他的后边
并移除旧的dom对象。
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]();
循环调用insertedVnodeQueue,并判断是否存在data,hook,inset,如果存在inset,则调用并传入insertedVnodeQueue[i]
这里的!是ts语法,判断是否为undefined,如果为undefined则不执行
接下来的for cbs.post依然是一个钩子函数。
return vnode;
返回了新虚拟dom节点。
加强
cbs的钩子是哪里来的?
我们可以搜索cbs
找到了
export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
const cbs: ModuleHooks = {
create: [],
update: [],
remove: [],
destroy: [],
pre: [],
post: [],
};
const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi;
for (const hook of hooks) {
for (const module of modules) {
const currentHook = module[hook];
if (currentHook !== undefined) {
(cbs[hook] as any[]).push(currentHook);
}
}
}
循环hooks,并获取每个module
那hooks是什么?
const hooks: Array<keyof Module> = [
"create",
"update",
"remove",
"destroy",
"pre",
"post",
];
那么这里就真相大白了
循环每个hook以及module
获取module下的hook钩子,并且投入到cbs对象下不同的数组内
在合适的情况下激发钩子。
结语
码头领稿把,给你置办卡地亚,2021,名满天下!