李恒道 发表于 2021-10-9 16:26:36

[油猴脚本开发指南]简明diff(三)

# 前文

上节课我们拿捏了patch函数,这节课继续往里深入拿捏

之前我们知道了

当新旧虚拟dom是相同的,则使用patchvnode函数比对

不同则删除旧元素,插入新元素。

那当相同的时候,patchvnode都干了什么呢?

开干!

先说一个结论,大家可以记忆下来

旧虚拟dom是目前网页的节点状态的抽象

新虚拟dom是将来网页的节点状态的抽象

我们需要在旧虚拟dom和新虚拟dom的比对过程中将其真实dom的状态更新至新虚拟dom。

如果理解这部分就说明已经难不倒你了

# 开始

首先看代码

```javascript
function patchVnode(
    oldVnode: VNode,
    vnode: VNode,
    insertedVnodeQueue: VNodeQueue
) {
    const hook = vnode.data?.hook;
    hook?.prepatch?.(oldVnode, vnode);
    const elm = (vnode.elm = oldVnode.elm)!;
    const oldCh = oldVnode.children as VNode[];
    const ch = vnode.children as VNode[];
    if (oldVnode === vnode) return;
    if (vnode.data !== undefined) {
      for (let i = 0; i < cbs.update.length; ++i)
      cbs.update(oldVnode, vnode);
      vnode.data.hook?.update?.(oldVnode, vnode);
    }
    if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
      } else if (isDef(ch)) {
      if (isDef(oldVnode.text)) api.setTextContent(elm, "");
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
      } else if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      } else if (isDef(oldVnode.text)) {
      api.setTextContent(elm, "");
      }
    } else if (oldVnode.text !== vnode.text) {
      if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      }
      api.setTextContent(elm, vnode.text!);
    }
    hook?.postpatch?.(oldVnode, vnode);
}
```

```javascript
    oldVnode: VNode,
    vnode: VNode,
    insertedVnodeQueue: VNodeQueue
```

传入新旧Vnode以及一个inseredVnodeQueue数组,这个将在执行完这个函数后循环触发钩子。

```javascript
    const hook = vnode.data?.hook;
    hook?.prepatch?.(oldVnode, vnode);
    const elm = (vnode.elm = oldVnode.elm)!;
    const oldCh = oldVnode.children as VNode[];
    const ch = vnode.children as VNode[];
```

这里获取了新node下的data属性的hook钩子函数

并判断是否存在,如果存在则判断prepatch是否存在,存在则调用这个函数触发回调钩子。

并将旧虚拟dom的真实dom复制给新的虚拟dom的elm属性上

分别获取新旧虚拟dom的子节点。

```javascript
if (oldVnode === vnode) return;
```

判断传入的新旧虚拟dom对象是否相等,如果相等则啥也不执行

```javascript
    if (vnode.data !== undefined) {
      for (let i = 0; i < cbs.update.length; ++i)
      cbs.update(oldVnode, vnode);
      vnode.data.hook?.update?.(oldVnode, vnode);
    }
```

依然是调用cbs的回调钩子,以及虚拟dom的data属性的钩子函数。

```javascript
    if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
      } else if (isDef(ch)) {
      if (isDef(oldVnode.text)) api.setTextContent(elm, "");
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
      } else if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      } else if (isDef(oldVnode.text)) {
      api.setTextContent(elm, "");
      }
    } else if (oldVnode.text !== vnode.text) {
      if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      }
      api.setTextContent(elm, vnode.text!);
    }
```

这里的逻辑相对复杂,我们一行一行来看

首先看看isundef函数以及isdef函数是啥

```javascript
function isUndef(s: any): boolean {
return s === undefined;
}
function isDef<A>(s: A): s is NonUndefined<A> {
return s !== undefined;
}
```

isundef判断是否等于undefine,isdef判断是否不等于undefined

```javascript
if (isUndef(vnode.text))
```

判断vnode.text是否为undefined,如果为undefined则执行括号内内容,不为则执行下部分。

因为一部分的内容相对较少,我们先分析vnode.text不为undefined的情况

注意,vnode只存在两种情况,一种是存在文字,一种是存在子节点,为undefined则为存在子节点,不为undefined则为文字

```javascript
else if (oldVnode.text !== vnode.text) {
      if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      }
      api.setTextContent(elm, vnode.text!);
    }
```

判断文字是否相等,如果相等则无需进行处理,不相等则进行处理

首先判断就旧节点是否不存在文字而存在子节点

如果存在子节点则移除

移除后

设置真实dom的文本为新虚拟dom的文本。

那么vnode.text存在的情况我们已经看完了,

```javascript
      if (isDef(oldCh) && isDef(ch)) {
      if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
      } else if (isDef(ch)) {
      if (isDef(oldVnode.text)) api.setTextContent(elm, "");
      addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
      } else if (isDef(oldCh)) {
      removeVnodes(elm, oldCh, 0, oldCh.length - 1);
      } else if (isDef(oldVnode.text)) {
      api.setTextContent(elm, "");
      }
```

这里判断了新旧虚拟dom的子节点存在情况,分别为四种情况,我们来逐步分析

第一个if如果判断成功,则为新旧虚拟dom都存在子节点

这时候判断是否相等,如果不相等,则调用updateChildren进行更新。

如果第一个if判断不成功,证明其中某一个或都不存在子节点。

第二个if判断了新虚拟dom是否存在子节点,根据第一个if结合推断

可以知道这种情况为新虚拟dom存在子节点,旧虚拟dom不存在子节点

这时候判断旧虚拟dom是否存在文字,如果存在则设置为空,并在真实dom上插入新虚拟dom的元素。

第三个if证明了新虚拟dom不存在子节点,并判断旧虚拟dom是否存在子节点

如果存在,则移除所有旧虚拟dom的节点。

那新虚拟dom的操作哪去了?

这里是

```javascript
if (isUndef(vnode.text))
```

判断成功的操作,新虚拟dom不存在文字,也不存在节点

第三种情况不存在任何新节点的插入

所以我们仅需移除旧节点即可。

```javascript
if (isDef(oldVnode.text)) {
      api.setTextContent(elm, "");
      }
```

第四种情况与第三种基本同理

只是旧虚拟dom不存在子节点

而存在文字

这时候我们设置为空即可

那么到这里我们已经走完了所有的if流程

最后完成

```javascript
hook?.postpatch?.(oldVnode, vnode);
```

触发hook的回调函数即可。

这里并不在我们研究diff的范畴内,所以暂且不表。
页: [1]
查看完整版本: [油猴脚本开发指南]简明diff(三)