Js实现简易虚拟Dom

76 阅读2分钟

1.概念:

  • Virtual DOM 是真实DOM的映射。
  • 当虚拟 DOM 树中的某些节点改变时,会得到一个新的虚拟树。算法对这两棵树(新树和旧树)进行比较,找出差异,然后只需要在真实的 DOM 上做出相应的改变。

 2.设计思路:

  •         虚拟Dom映射到真实Dom的工具函数 createElement(…) ,将js对象映射为Dom对象
  •         比较两棵虚拟DOM树的差异并更新的工具函数updateElement(…) ,包括新节点的添加,老节点的移除以及节点的替换;

 3.实现:

  •  createElement(…),利用document.createTextNode 、document.createElement实现文本节点和dom节点的基本创建

/**
 * @description 传入node类型的js对象数据,生成Dom
 * @param node node类型js对象数据 节点数据
 */
function createElement(node) {
  if (typeof node === 'string') {
    return document.createTextNode(node);
  } else {
      const $el = document.createElement(node.type);
      node.children.map(createElement).forEach($el.appendChild.bind($el));
      return $el;
  }

 node的类型为:

   const node = {
     type: "div", // 标签名或者是纯文本
     props: {}, // 扩展的功能,如class、id等属性的添加
     children: [],// node 类型的js对象数组
   };
  • updateElement(…),利用createElement,添加一些条件判断 ,实现dom树的渲染、更新,应当考虑3个参数,$parent, newNode, oldNode ,稍后介绍,$parent是父节点

 首先是新节点的添加:

 // 不存在老节点,则直接创建新节点
function updateElement($parent, newNode, oldNode,index) {
  if (!oldNode) {
    $parent.appendChild(createElement(newNode));
  }
}

然后是存在oldNode存在而newNode为空的情况: 

    // 实现移除老节点的操作,index为该节点在父节点的子节点数组中的位序
    function updateElement($parent, newNode, oldNode, index) {
      if (!oldNode) {
        $parent.appendChild(createElement(newNode));
      } else if (!newNode) {
        $parent.removeChild($parent.childNodes[index]);
      }
    }

 当oldNode,newNode都存在时:

    // 节点的替换操作,
    function updateElement($parent, newNode, oldNode, index = 0) {
      if (!oldNode) {
        $parent.appendChild(createElement(newNode));
      } else if (!newNode) {
        $parent.removeChild($parent.childNodes[index]);
      } else if (changed(newNode, oldNode)) {
        $parent.replaceChild(createElement(newNode), $parent.childNodes[index]);
      }
    }
    // 需要定义一个工具函数changed(newNode, oldNode),用于比较新老节点之间的差异
    function changed(node1, node2) {
      return typeof node1 !== typeof node2 ||
             typeof node1 === ‘string’ && node1 !== node2 ||
             node1.type !== node2.type
    }

当新旧节点没有差异时,需要调用updateElement实现递归操作其子节点:

    // 完整的节点创建、更新函数
    function updateElement($parent, newNode, oldNode, index) {
      if (!oldNode) {
        $parent.appendChild(createElement(newNode));
      } else if (!newNode) {
        $parent.removeChild($parent.childNodes[index]);
      } else if (changed(newNode, oldNode)) {
        $parent.replaceChild(createElement(newNode), $parent.childNodes[index]);
      } else if (newNode.type) {
        const newLength = newNode.children.length;
        const oldLength = oldNode.children.length;
        for (let i = 0; i < newLength || i < oldLength; i++) {
          updateElement(
            $parent.childNodes[index],
            newNode.children[i],
            oldNode.children[i],
            i
          );
        }
      }
    }

参考链接:通过编写简易虚拟DOM,来学习虚拟DOM 的知识!