李恒道 发表于 2021-10-9 00:46:40

[油猴脚本开发指南]简明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]
查看完整版本: [油猴脚本开发指南]简明diff(二)