简单实现Vue中的虚拟dom

简单实现Vue中的虚拟dom

传送门:简单实现Vue中的插值替换(三)

前言:

  1. 要想简单实现虚拟dom,首先我们要了解虚拟dom,知道自己要实现的是个什么东西。

    说起来,我刚开始学习Vue的时候对虚拟dom也是一知半解,我当时一听虚拟dom,脑子里第一反应就是虚拟机 ,不因为什么,就因为它两个都带有虚拟这个词。

    虚拟什么意思?虚拟:设想、虚构。

    说的更直白一点,就是凭想象编造出来的事物。

  2. 既然是虚拟出来的,那虚拟出来了个啥呢,或者说Vue根据真实的dom编造出来了一个什么样的东西呢?

    其实,简单的理解虚拟dom,就是Vue用对象式的语言,把一个真实的dom描述了一下。

    • 我们看一个简单的例子:

      <div id="root">
          <p class="name" title="Michel">Michel</p>
      </div>
      

      这是一段正常的dom,那么Vue里面是怎么描述它的呢?我们再来看一下虚拟dom:

      【注:这里让大家看的虚拟dom远远没有Vue转换的那么复杂,只是简单些的,好理解。】

      {
          tag:"div", // 标签
          attrs:{id:"root"}, // 属性
          type:1,// 元素类型
          value: undefined, // 值
          children:[ // 孩子元素
              {
                  tag:"p",
                  attrs:{class:"name", title:"Michel"},
                  type:1,
                  value: undefined,
                  children:[
                      {
                          tag: undefined,
                          attrs: undefined,
                          type: 3,
                          value: 'Michel',
                          children:[]
                      }
                  ]
              }
          ]
      }
      

      看了这个虚拟dom,是不是感觉很简单,一个包含着 tag,attrs,type,value,children的对象,就把这个dom描述出来了,而且层级也描述的很清楚。

    • 这就是虚拟dom,大家还感觉很神秘吗?哈哈。

  3. 说道这里,又得简单的啰嗦两句,为啥要用虚拟dom???

    当然是为了更快的编译,更高运行效率。【diff算法比较的就是虚拟dom】


接下来,我们进入正题,如何简简单单的把一串真正的dom转换成虚拟dom呢?

要分以下几步

  1. 首先要拿到真正的dom模板

  2. 解析它,首先获取这个dom的类型,这个看过我前两篇博客的人估计不会陌生,就是对元素标签和文本进行一下区分。

  3. 如果是元素标签:

    那就先获取这个dom的标签名,然后获取它的属性,把这些连同它的类型都给存起来,然后看看它有没有孩子,如果有,那就拿到它的孩子,然后就进入递归喽。

  4. 如果是文本:

    那就更简单了,我们就直接拿到它的值,然后连同类型存起来。

  5. 当然怎么存,也是有讲究的,我们可以封装一个函数来专门干存储这个事,当然定义一个class类也行。

现在知道怎么干,那下面就填充代码呗

  • 获取dom模板

    doument.getElemetById('#root'),看过我前面博客的知道,这个其实有个简单方法,那就是直接写root就好了。

  • 获取dom类型

    node.nodeType就可以拿到啦

  • 如果是元素类型,就是类型是 1:

    node.nodeName 获取标签名
    
    node.attributes 获取属性名,这里得到的是一个伪数组,所以我们还需要处理一下,处理成以属性名为key,属性值为value的一个对象
    
    node.childNodes 获取子元素,然后就进入递归了
    
  • 如果是文本,就是类型 3,那就 node.nodeValue 获取一下值,然后连同类型存起来。

  • 存储的话,比如这样:

    // 定义一个类
    class vNode {
        constructor(tag, attrs, value, type) {
            this.tag = tag && tag.toLowerCase();
            this.attrs = attrs;
            this.value = value;
            this.type = type;
            this.children = [];
        }
    
        appendChildArr(vNode) {
            this.children.push(vNode);
        }
    }
    // 然后使用的时候,new vNode 传值就好了。
    // 函数的就不写啦,和我前两篇博客定义的函数形式很像啦。
    

最后我们来看下完整的代码

<body>
    <div id="root">
        <p class="name" title="Michel">Michel</p>
        <p class="age" title="Michel今年21岁">21</p>
    </div>
</body>
<script>
    console.log('真实dom', root);
    // 定义一个类
    class vNode {
        constructor(tag, attrs, value, type) {
            this.tag = tag && tag.toLowerCase(); // 转换成小写
            this.attrs = attrs;
            this.value = value;
            this.type = type;
            this.children = []; // 定义一个数组,存储转换之后的虚拟DOM
        }

        appendChildArr(vNode) {
            this.children.push(vNode);
        }
    }

    // 转换虚拟dom
    const getVNode = (node) => {
        let nodeType = node.nodeType;//获取dom元素类型
        let _vnode = null;

        if (nodeType === 1) {
            let nodeName = node.nodeName;//获取dom标签名
            let attrs = node.attributes;//获取dom属性【获取到的是一个伪数组】
            let _attrObj = {};// 定义存储属性的对象
            for (let i = 0; i < attrs.length; i++) {
                _attrObj[attrs[i].nodeName] = attrs[i].nodeValue; // 以属性名为key,属性值为value
            }
            _vnode = new vNode(nodeName, _attrObj, undefined, nodeType); // 存一下

            let childNodes = node.childNodes; // 拿到子元素
            for (let i = 0; i < childNodes.length; i++) {
                _vnode.appendChildArr(getVNode(childNodes[i])); // 递归
            }
        } else if (nodeType === 3) {
            _vnode = new vNode(undefined, undefined, node.nodeValue, nodeType);
        }

        return _vnode;
    }

    let vRoot = getVNode(root);
    console.log('虚拟dom', vRoot);
</script>

OK,我们好说歹说,也算简单的实现了虚拟dom,所以接下来,我们要做的就是,我们怎么给转换回去,毕竟不能干一半是不是。

其实转换回来也简单,大致分以下几步:

  1. 拿到虚拟dom对象之后,首先就是拿到type,就是节点类型。

  2. 然后就还是判断,节点类型如果是 1,那就直接创建文本节点了:

    document.createTextNode(node.value)// 把节点的值穿进去就行了
    
  3. 如果是元素节点 3

    • 首先根据标签名创建一个dom:

      document.createElement(vnode.tag); 
      
    • 然后呢,就是拿到存着属性的对象,然后遍历它,通过setAttribute来给创建的这个dom标签设置属性。

    • 最后属性也设置完了,那就在去遍历children数组,以同样的方法依法炮制它的子节点,然后通过appendChild的方式给插到这个标签里。

      所以这里,就还是递归,嘿嘿。

说到这里,我们就来看一下转换成真实dom的完整代码:

// 将虚拟dom转换为真实的dom
const parseNode = (vnode) => {
    let type = vnode.type; // 获取虚拟dom的type类型
    let _node = null;

    if (type === 3) {
        return document.createTextNode(vnode.value); // 创建文本节点
    } else if (type === 1) {
        _node = document.createElement(vnode.tag); // 创建标签
        let attrs = vnode.attrs;
        // 遍历属性对象
        Object.keys(attrs).forEach(key => {
            let attrName = key;
            let attrValue = attrs[key];
            _node.setAttribute(attrName, attrValue); // 设置属性
        })
    }
    // 子元素
    let children = vnode.children;
    children.forEach(item => {
        _node.appendChild(parseNode(item));//递归遍历子节点然后插入到父节点里
    });
    return _node;
}

let rRoot = parseNode(vRoot);
console.log('虚拟dom转换的真实dom', rRoot);

这就是我这篇博客要讲的内容了,唉,想起来明天还要加班【'mmp 艹 艹 艹 ',咳咳,大家看,这是我写的一个字符串。】

下一篇博客的话,我们简简单单来说一下,Vue怎么把一个对象搞成响应式的。

传送门:简单实现Vue中的响应式对象

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值