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

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

[复制链接]
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    620

    主题

    5085

    回帖

    5959

    积分

    管理员

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

    积分
    5959

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

    发表于 2021-10-9 00:46:40 | 显示全部楼层 | 阅读模式

    前文

    之前我们聊到了diff是同级比较

    那同级比较到底做了什么呢?

    这里我采取最为普遍的Snabbdom源码来与大家一起进行探讨

    原因有三

    1.市面上的采取diff的源码其实大同小异,我们选取知识最多的一个源码分析

    2.Vue采取了Snabbdom的基础上进行了修改,学Snabbdom=学Vue的diff

    3.我他妈学的Snabbdom,没学过其他的

    源码可以在https://github.com/snabbdom/snabbdom看

    进入这个页面的上方可以通过

    图片.png

    搜索源码。

    关于如何定位可以查看开发文档,然后再翻阅源码。

    这里我直接给出了,是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,名满天下!

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

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

    发表回复

    本版积分规则

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