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