DOM与虚拟DOM

DOM与虚拟DOM

前段时间在学react,刚接触DOM被搞得迷迷糊糊的,为此还和实验室的老师争论了一番,也参考了一些资料,记录一下自己的理解吧。

一、vdom是什么

vdom是虚拟DOM(Virtual DOM)的简称,指的是用JS模拟的DOM结构,将DOM变化的对比放在JS层来做。换句话说,vdom就是JS对象。

举个栗子,下面的DOM结构:

<ul id="list">
    <li class="item">Item1</li>
    <li class="item">Item2</li>
</ul>

映射为虚拟DOM是这样式儿的:

{
    tag: "ul",
    attrs: {
        id: "list"
    },
    children: [
        {
            tag: "li",
            attrs: { className: "item" },
            children: ["Item1"]
        },
        {
            tag: "li",
            attrs: { className: "item" },
            children: ["Item2"]
        }
    ]
}
二、为什么要用vdom

比如说现在要记录同学们的成绩(LXL好惨啊)

[
    {
        name: "赵大哥",
        class: "1",
        score: "100"
    },
    {
        name: "小窦",
        class: "2",
        score: "100"
    },
    {
        name: "LXL",
        class: "3",
        score: "59"
    },
     {
        name: "Ms_Xiang",
        class: "4",
        score: "100"
    },
]

想要实现修改信息时表格跟着修改,用jQuery实现如下:

<!DOCTYPE html>
<html lang = 'en'>
<head>
  <meta charset="UTF-8">
  <title>Table</title>
</head>

<body>
  <div id="container"></div>
  <button id="btn-change">Change here</button>
  <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
  <script>
    const data = [ {
        name: "赵大哥",
        class: "1",
        score: "100"
    },
    {
        name: "小窦",
        class: "2",
        score: "100"
    },
    {
        name: "LXL",
        class: "3",
        score: "59"
    },
     {
        name: "Ms_Xiang",
        class: "4",
        score: "100"
    },
    ];
    //渲染函数
    function render(data) {
      const $container = $('#container');
      $container.html('');
      const $table = $('<table>');
      // 重绘一次
      $table.append($('<tr><td>name</td><td>class</td><td>score</td></tr>'))
      data.forEach(item => {
        //每次进入都重绘
        $table.append($(`<tr><td>${item.name}</td><td>${item.class}</td><td>${item.score}</td></tr>`))
      })
      $container.append($table);
    }

    $('#btn-change').click(function () {
      data[2].class = 1;
      data[2].score = parseInt(Math.random()*40+61); 
      render(data);
    });
  </script>
</body>
</html>

果然,LXL换了班以后就能及格了

这样每次点击按钮,都会有相应的视图的变化,但是审查一下元素,每次改动之后,不管当前数据是否和以前数据一样,table标签都要重新渲染一次,而DOM重绘会消耗浏览器的性能。
在这里插入图片描述

所以,如果采用JS对象模拟的办法,将DOM的对比操作放在JS层,减少浏览器不必要的重绘,就可以提高效率了。

当然不排除极端情况,表格中的每个数据都需要重绘,这时候虚拟DOM就当场翻车了,它甚至比DOM多了一层diff算法,所以在这种情况下虚拟DOM并不比DOM快。

但是,虚拟DOM还是有自身独特的优势的:

1、它打开了函数式的UI编程的大门,即UI = f(data)这种构建UI的方式。

2、可以将JS对象渲染到浏览器DOM以外的环境中,也就是支持了跨平台开发,比如ReactNative。

三、使用snabbdom实现vdom

snabbdom地址在这https://link.zhihu.com/?target=https%3A//github.com/snabbdom/snabbdom

这是一个简易的实现vdom功能的库,相对于vue 和 react,这个库更适合研究vdom(更简易)。vdom有两个核心的api:h函数和patch函数,h函数用来生成vdom对象,patch函数用来做与虚拟DOM的对比和将vdom挂载到真实的DOM上。

用snabbdom重写一下刚才的分数表:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>table</title>
</head>
<body>
  <div id="container"></div>
  <button id="btn-change">Change here</button>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.min.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
  <script>
    let snabbdom = window.snabbdom;

    // 定义patch
    let patch = snabbdom.init([
      snabbdom_class,
      snabbdom_props,
      snabbdom_style,
      snabbdom_eventlisteners
    ]);

    //定义h
    let h = snabbdom.h;

    const data = [ {
        name: "赵大哥",
        class: "1",
        score: "100"
    },
    {
        name: "小窦",
        class: "2",
        score: "100"
    },
    {
        name: "LXL",
        class: "3",
        score: "59"
    },
     {
        name: "Ms_Xiang",
        class: "4",
        score: "100"
    },
    ];
    data.unshift({name: "name", class: "class", score: "score"});

    let container = document.getElementById('container');
    let vnode;
    const render = (data) => {
      let newVnode = h('table', {}, data.map(item => { 
        let tds = [];
        for(let i in item) {
          if(item.hasOwnProperty(i)) {
            tds.push(h('td', {}, item[i] + ''));
          }
        }
        return h('tr', {}, tds);
      }));

      if(vnode) {
          patch(vnode, newVnode);
      } else {
          patch(container, newVnode);
      }
      vnode = newVnode;
    }

    render(data);

    let btnChnage = document.getElementById('btn-change');
    btnChnage.addEventListener('click', function() {
      data[3].class = 1;
      data[3].score = parseInt(Math.random()*40+61); 
      render(data);
    })
  </script>
</body>
</html>

发现只有被更改的地方发生了变化 ,这样就节省了浏览器的开销。

四、diff算法
1.什么是diff算法

diff算法,即比较两段文本之间差异的一种算法,其实这个算法在Linux的diff命令中已有体现,并且我们常用的git-diff算法也是它。

2.vdom为啥要用diff算法

因为所以,科学道理。 DOM的操作是非常昂贵的,因此我们要尽可能的减少DOM操作,每次只需要找出本次DOM操作必须更新的节点去更新,其他的不更新,这个过程就需要用到diff算法。

3.vdom中diff算法的简易实现

将vdom转化为真实dom:

const createElement = (vnode) => {
  let tag = vnode.tag;
  let attrs = vnode.attrs || {};
  let children = vnode.children || [];
  if(!tag) {
    return null;
  }
  //创建元素
  let elem = document.createElement(tag);
  //属性
  let attrName;
  for (attrName in attrs) {
    if(attrs.hasOwnProperty(attrName)) {
      elem.setAttribute(attrName, attrs[attrName]);
    }
  }
  //子元素
  children.forEach(childVnode => {
    //给elem添加子元素
    elem.appendChild(createElement(childVnode));
  })

  //返回真实的dom元素
  return elem;
}

用简易diff算法做更新操作:

function updateChildren(vnode, newVnode) {
  let children = vnode.children || [];
  let newChildren = newVnode.children || [];

  children.forEach((childVnode, index) => {
    let newChildVNode = newChildren[index];
    if(childVnode.tag === newChildVNode.tag) {
      //深层次对比, 递归过程
      updateChildren(childVnode, newChildVNode);
    } else {
      //替换
      replaceNode(childVnode, newChildVNode);
    }
  })
}

嗯,差不多了,收工hhh~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值