Vue2的双端 Diff算法

<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="UTF-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>Vue2的双端 Diff算法</title>

    <style>

      .bgColor {

        background-color: deeppink;

      }

      .fontColor {

        color: #fff;

        font-size: 24px;

      }

      p {

        width: 400px;

        padding: 5px 0;

        font-size: 24px;

        font-weight: bold;

        color: blue;

        text-align: center;

        border: 1px solid #eee;

        margin-top: 10px;

        margin: 0 auto;

      }

    </style>

  </head>

  <body>

    <div id="app"></div>

    <script>

      /**

       * 定义文本节点和注释节点

       */

      const Text = Symbol();

      const Comment = Symbol();

      const Fragment = Symbol();

      const newVnode1 = {

        type: Text,

        children: "我是文本内容 ",

      };

      const newVnode2 = {

        type: Comment,

        children: "我是注释内容",

      };

      const fragmentNode = {

        type: "ul",

        children: [

          {

            type: Fragment,

            children: [

              { type: "li", children: "item 1" },

              { type: "li", children: "item 2" },

              { type: "li", children: "item 3" },

            ],

          },

        ],

      };

      /**

       * 自定义渲染器

       */

      function createRenderer(options) {

        const {

          createElement,

          insert,

          setElementText,

          patchProps,

          createText,

          setText,

        } = options;

        /**

         * 卸载

         */

        function unmount(vnode) {

          if (vnode.type === Fragment) {

            vnode.children.forEach((c) => unmount(c));

            return;

          }

          const parent = vnode.el.parentNode;

          if (parent) {

            parent.removeChild(vnode.el);

          }

        }

        function render(vnode, container) {

          if (vnode) {

            patch(container._vnode, vnode, container);

          } else {

            // 删除旧节点

            if (container._vnode) {

              unmount(container._vnode);

            }

          }

          container._vnode = vnode;

        }

        /**

         * 打补丁

         */

        function patch(n1, n2, container, anchor) {

          if (n1 && n1.type !== n2.type) {

            unmount(n1);

            n1 = null;

          }

          const { type } = n2;

          //   console.log(type);

          if (typeof type === "string") {

            if (!n1) {

              mountElement(n2, container, anchor);

            } else {

              patchElement(n1, n2);

            }

          } else if (type === Text) {

            // console.log("文本节点");

            // 文本节点

            if (!n1) {

              const el = (n2.el = createText(n2.children));

              insert(el, container);

            } else {

              const el = (n2.el = n1.el);

              if (n2.children !== n1.children) {

                setText(el, n2.children);

              }

            }

          } else if (type === Fragment) {

            // 片断

            if (!n1) {

              // 将Fragment的 children逐个挂载

              n2.children.forEach((c) => patch(null, c, container));

            } else {

              patchChildren(n1, n2, container);

            }

          }

        }

        /**

         * 更新子节点

         */

        function patchElement(n1, n2) {

          const el = (n2.el = n1.el);

          const oldProps = n1.props;

          const newProps = n2.props;

          for (const key in newProps) {

            if (newProps[key] !== oldProps[key]) {

              patchProps(el, key, oldProps[key], newProps[key]);

            }

          }

          for (const key in oldProps) {

            if (!(key in newProps)) {

              patchProps(el, key, oldProps[key], null);

            }

          }

          patchChildren(n1, n2, el);

        }

        /**

         * 抽离封装核心 Diff算法

         */

        function diff(n1, n2, container) {

          console.log("此处是核心的 Diff算法");

          console.log(n1);

          console.log(n2);

          const oldChildren = n1.children;

          const newChildren = n2.children;

          let oldStartIdx = 0;

          let oldEndIdx = oldChildren.length - 1;

          let newStartIdx = 0;

          let newEndIdx = newChildren.length - 1;

          let oldStartVNode = oldChildren[oldStartIdx];

          let oldEndVNode = oldChildren[oldEndIdx];

          let newStartVNode = newChildren[newStartIdx];

          let newEndVNode = newChildren[newEndIdx];

          while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {

            if (!oldStartVNode) {

              oldStartVNode = oldChildren[++oldStartIdx];

            } else if (!oldEndVNode) {

              oldEndVNode = oldChildren[--oldEndIdx];

            } else if (oldStartVNode.key === newStartVNode.key) {

              // 打补丁

              patch(oldStartVNode, newStartVNode, container);

              oldStartVNode = oldChildren[++oldStartIdx];

              newStartVNode = newChildren[++newStartIdx];

            } else if (oldEndVNode.key === newEndVNode.key) {

              // 打补丁

              patch(oldEndVNode, newEndVNode, container);

              // 更新索引值

              oldEndVNode = oldChildren[--oldEndIdx];

              newEndVNode = newChildren[--newEndIdx];

            } else if (oldStartVNode.key === newEndVNode.key) {

              // 打补丁

              patch(oldStartVNode, newEndVNode, container);

              // 移动DOM节点

              insert(oldStartVNode.el, container, oldEndVNode.el.nextSibling);

              // 更新索引值

              oldStartVNode = oldChildren[++oldStartIdx];

              newEndVNode = newChildren[--newEndIdx];

            } else if (oldEndVNode.key === newStartVNode.key) {

              // 打补丁

              patch(oldEndVNode, newStartVNode, container);

              // 移动 DOM 操作

              insert(oldEndVNode.el, container, oldStartVNode.el);

              // 更新索引值

              oldEndVNode = oldChildren[--oldEndIdx];

              newStartVNode = newChildren[++newStartIdx];

            } else {

              // 非理想状况的处理方式

              const idxInOld = oldChildren.findIndex(

                (node) => node.key === newStartVNode.key

              );

              if (idxInOld > 0) {

                const vnodeToMove = oldChildren[idxInOld];

                // console.log("vnodeToMove", vnodeToMove);

                patch(vnodeToMove, newStartVNode, container);

                insert(vnodeToMove.el, container, oldStartVNode.el);

                oldChildren[idxInOld] = undefined;

              } else {

                // 添加新元素

                patch(null, newStartVNode, container, oldStartVNode.el)

              }

              newStartVNode = newChildren[++newStartIdx];

            }

          }

          // 循环结束后检查索引值的情况

          if (oldEndIdx < oldStartIdx && newStartIdx <= newEndIdx) {

            // 添加新元素

            for(let i = newStartIdx; i <= newEndIdx; i++) {

              patch(null, newChildren[i], container, oldStartVNode.el)

            }

          } else if(newEndIdx < newStartIdx && oldStartIdx <= oldEndIdx) {

            // 移除不存在的节点

            for(let i = oldStartIdx; i <= oldEndIdx; i++) {

              unmount(oldChildren[i])

            }

          }

        }

        /**

         * 更新children

         */

        function patchChildren(n1, n2, container) {

          if (typeof n2.children === "string") {

            if (Array.isArray(n1.children)) {

              n1.children.forEach((c) => unmount(c));

            }

            setElementText(container, n2.children);

          } else if (Array.isArray(n2.children)) {

            if (Array.isArray(n1.children)) {

              // 此处是核心的 Diff算法

              diff(n1, n2, container);

            } else {

              setElementText(container, "");

              // 挂载新节点

              n2.children.forEach((c) => patch(null, c, container));

            }

          } else {

            // 新子节点不存在,则卸载旧节点

            if (Array.isArray(n1.children)) {

              n1.children.forEach((c) => unmount(c));

            } else if (typeof n1.children === "string") {

              setElementText(container, "");

            }

          }

        }

        // 主要改动这里

        function mountElement(vnode, container, anchor) {

          const el = (vnode.el = createElement(vnode.type));

          if (typeof vnode.children === "string") {

            setElementText(el, vnode.children);

          } else if (Array.isArray(vnode.children)) {

            vnode.children.forEach((child) => {

              patch(null, child, el);

            });

          }

          if (vnode.props) {

            for (const key in vnode.props) {

              const value = vnode.props[key];

              patchProps(el, key, null, value);

            }

          }

          insert(el, container, anchor);

        }

        function shouldSetAsProps(el, key, value) {

          if (key === "form" && el.tagName === "INPUT") {

            return false;

          }

          return key in el;

        }

        return { render };

      }

      /**

       * 公共函数

       */

      function shouldSetAsProps(el, key, value) {

        if (key === "form" && el.tagName === "INPUT") {

          console.log("el", el);

          return false;

        }

        return key in el;

      }

      // 测试代码

      const renderer = createRenderer({

        createElement(tag) {

          return document.createElement(tag);

        },

        setElementText(el, text) {

          el.textContent = text;

        },

        insert(el, parent, anchor = null) {

          parent.insertBefore(el, anchor);

        },

        createText(text) {

          return document.createTextNode(text);

        },

        setText(el, text) {

          el.nodeValue = text;

        },

        patchProps(el, key, prevValue, nextValue) {

          if (/^on/.test(key)) {

            // const name = key.slice(2).toLowerCase();

            // prevValue && el.removeEventListener(name, prevValue)

            // el.addEventListener(name, nextValue);

            const invokers = el._vei || (el._vei = {});

            let invoker = invokers[key];

            const name = key.slice(2).toLowerCase();

            if (nextValue) {

              if (!invoker) {

                // 绑定一个伪造的事件处理函数invoker

                invoker = el._vei[key] = (e) => {

                  // e.timeStamp 触发事件的时间

                  if (e.timeStamp < invoker.attached) {

                    return;

                  }

                  if (Array.isArray(invoker.value)) {

                    invoker.value.forEach((fn) => fn(e));

                  } else {

                    invoker.value(e);

                  }

                };

                invoker.value = nextValue;

                // 绑定事件的时间

                invoker.attached = performance.now();

                el.addEventListener(name, invoker);

              } else {

                invoker.value = nextValue;

              }

            } else {

              el.removeEventListener(name, invoker);

            }

          } else if (key === "class") {

            el.className = nextValue || "";

          } else if (shouldSetAsProps(el, key, nextValue)) {

            const type = typeof el[key];

            if (type === "boolean" && nextValue === "") {

              el[key] = true;

            } else {

              el[key] = nextValue;

            }

          } else {

            el.setAttribute(key, nextValue);

          }

        },

      });

      // 虚拟节点对象

      const oldVnode = {

        type: "div",

        children: [

          { type: "p", children: "1", key: 1 },

          { type: "p", children: "2", key: 2 },

          { type: "p", children: "3", key: 3 },

        ],

      };

      const newVnode = {

        type: "div",

        children: [

          { type: "p", children: "1", key: 1 },

          { type: "p", children: "3", key: 3 },

        ],

      };

      renderer.render(oldVnode, document.querySelector("#app"));

      setTimeout(() => {

        renderer.render(newVnode, document.querySelector("#app"));

      }, 2000);

    </script>

  </body>

</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值