上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖
楼主: 李恒道 - 

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

[复制链接]
  • TA的每日心情
    慵懒
    2024-10-28 07:07
  • 签到天数: 193 天

    [LV.7]常住居民III

    712

    主题

    5959

    回帖

    6758

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6758

    荣誉开发者喜迎中秋油中2周年生态建设者

    发表于 2021-10-9 16:26:36 | 显示全部楼层 | 阅读模式

    前文

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

    之前我们知道了

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

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

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

    开干!

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

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

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

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

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

    开始

    首先看代码

      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[i](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);
      }
        oldVnode: VNode,
        vnode: VNode,
        insertedVnodeQueue: VNodeQueue

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

        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的子节点。

    if (oldVnode === vnode) return;

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

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

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

        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函数是啥

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

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

    if (isUndef(vnode.text))

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

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

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

     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存在的情况我们已经看完了,

          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的操作哪去了?

    这里是

    if (isUndef(vnode.text)) 

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

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

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

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

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

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

    而存在文字

    这时候我们设置为空即可

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

    最后完成

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

    触发hook的回调函数即可。

    这里并不在我们研究diff的范畴内,所以暂且不表。

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表