js 虚拟DOM 原理展示

<style>
  #main {
    border: 1px solid #666;
    padding: 5px;
  }

  .red {
    color: red;
  }
</style>
 <div id="main"> </div>
// 01思路: 虚拟dom是个对象,上面是对标签数据的描述
    // tag:标签类型  props:标签属性  child子元素(字符串|VnDOm)
    const VnDOm = (tag, props, child = []) => ({ tag, props, child });

    // 02数据: 生成虚拟DOM树
    const createVnDOm = (list) => {
      const childData = list.map(item => VnDOm('li', { class: 'red', title: item }, item));
      const ulData = VnDOm('ul', { class: 'content' }, childData);
      return ulData;
    };

    const createVnDOm1 = (list) => {
      const childData = list.map((item, index) => VnDOm('li', { class: 'red', title: item }, item));
      const ulData = VnDOm('ul', { class: 'changeContent' }, childData);
      return ulData;
    };

    // 03真实DOM : 将VoDOM挂载根DOM 
    // root:根节点   resDOM:虚拟DOM
    const mountVnDOm = (root, resDOM) => {

      if (typeof resDOM == "string") {
        return root.innerHTML = resDOM;
      }

      // 标签元素
      const element = document.createElement(resDOM.tag);

      // 将真实DOM绑定在resDOM,这样在比对数据的时候,可以方便的更新该节点数据对应的真实DOM
      resDOM.element = element;

      // 挂载属性
      const domAttrs = resDOM.props;
      if (domAttrs) {
        for (const [key, val] of Object.entries(domAttrs)) {
          element.setAttribute(key, val);
        };
      }

      // 遍历子元素
      const childData = resDOM.child;
      if (typeof childData == "string") {
        element.innerHTML = childData;
      } else {
        childData.forEach(item => {
          mountVnDOm(element, item);
        });
      };

      // 挂载子元素
      if (typeof root == "string") {
        const rootDOM = document.querySelector(root); // 根元素
        rootDOM.appendChild(element);
      } else {
        root.appendChild(element)
      }
    };

    // 04数据比对: 新的VnDom和旧VnDom找出发生变化的数据 => 映射到视图
    const patchFn = (oldVnDOm, newVnDOm) => {

      // 将虚拟DOM对应的真实元素,都指向同一个,便于后面修改
      const element = (newVnDOm.element = oldVnDOm.element);

      // 跟节点标签不同,删除根节点DOM,重新渲染新的数据
      if (oldVnDOm.tag !== newVnDOm.tag) {
        const root = element.parentNode;
        mountVnDOm(root, newVnDOm);
        root.removeChild(element);
      } else { // 比较每个子节点数据

        const oldVnDOmChild = oldVnDOm.child;
        const newVnDOmChild = newVnDOm.child;

        // 更新节点属性
        for (const [key, val] of Object.entries(newVnDOm.props)) {
          element.setAttribute(key, val)
        };

        // 子节点字符串,并且内容不一样
        if (typeof newVnDOmChild == "string" && (oldVnDOmChild !== newVnDOmChild)) {
          element.innerHTML = newVnDOmChild;
        }

        // 子节点数组
        if (Array.isArray(oldVnDOmChild)) {

          // 数据长度
          const oldSize = oldVnDOmChild.length;
          const newSize = newVnDOmChild.length;

          // 使用最小长度数据作为依据(长度一致返回同一个值),进行比较
          const commonLen = Math.min(oldSize, newSize);
          for (let i = 0; i < commonLen; i++) {
            patchFn(oldVnDOmChild[i], newVnDOmChild[i])
          };

          // 数据不相等时候 => 处理比较之后,未处理的剩下数据
          // I: oldVnDOm渲染4条数据,newVnDOm数据2条,则需要处理oldVnDOm剩余的2个数据(删除)
          if (oldSize > newSize) {
            oldVnDOmChild.slice(newSize).forEach(val => val.element.parentNode.removeChild(val.element))
          }

          // II: oldVnDOm渲染2条数据,newVnDOm数据4条,则需要处理newVnDOm剩余的2个数据(增加)
          if (newSize > oldSize) {
            // element => 虚拟DOM数据上由于没有绑定element,则此处的element为上级DOM
            newVnDOmChild.slice(oldSize).forEach(val => {
              mountVnDOm(element, val);
            })
          }
        }
      }
    };

    // I: 第一次直接渲染数据
    const VnDOmData1 = createVnDOm(['apple', 'pea', 'age']);
    mountVnDOm('#main', VnDOmData1);

    // II: 数据更新 => 需要patchFn函数找出需要更新的函数
    setTimeout(() => {
      const VnDOmData2 = createVnDOm1(['apple', '桃子', '名字', '变化的6']);
      patchFn(VnDOmData1, VnDOmData2);
    }, 1000);

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值