虚拟Dom

Virtual Dom

  • vdom 是vue和react的核心
  • vdom是什么东西,有什么用,为什么会存在vdom?
  • vdom如何应用,核心API是什么?
  • diff算法

## 什么是vdom ##

  • 用js模拟DOM结构
  • DOM变化的对比,放在JS层来做
  • 提高重绘性能
<ul id="list">
   <li class="item">Item 1</li>
   <li class="item">Item 2</li>
</ul>

用js来模拟

{
   tag:"ul",
   attrs:{
     id:"list"
   },
   children:[
    {
       tag:"li",
       attrs:{ className: "item"},  //class是js的保留字,所以用className
       children:['Item 1']  
    },{
       tag:"li",
       attrs:{ className: "item"},
       children:['Item 2']  
      }
    ]
 }

设计一个需求场景,渲染一个数组成表格

//Jquery的实现
 <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script>
        <script type="text/javascript">
           var dataList = [
               {
                   name:'111',
                   age:1
               },{
                   name:'222',
                   age:2
               },{
                   name:'333',
                   age:3
               },{
                   name:'444',
                   age:4
               },
           ]
           $(document).ready(function () {
            function render(data){
               var $container = $('#container')
               $container.html('')
               //拼接tabel
               $table = $('<table>')
               $table.append($('<tr><td>name</td><td>age</td></tr>'))
               //渲染到页面
               data.forEach(item => {
                $table.append($(`<tr><td>${item.name}</td><td>${item.age}</td></tr>`))
               });
               $container.append($table)
           }  
            render(dataList)
            $("#btn-change").click(function(){
               dataList[1].age=30  //每次修改数据都会清空dom,然后重绘表格
               render(dataList)
            })
           })
        
        </script>
    </head>
    <body>
        <div id="container"></div>
        <button id="btn-change">修改数据</button>
    </body>
    </html>

上述办法遇到的问题

  • js原生或者是Jquery框架时代,都是直接操作DOM节点来进行渲染页面,可是这样的代价确实是很大,需要将原本的DOM全部清除,然后在重新渲染一遍
  • 操作Dom非常昂贵。每个Dom自带了太多的属性。 js运行效率高
  • 尽量减少Dom操作
  • 项目越复杂,运行效率越低,影响越严重
  • vdom 可以解决这个问题,将Dom操作方在Js层,提高效率

vdom如何应用,核心API

  • snabbdom
    为什么是snabbdom.js
    由于虚拟dom有那么多的好处而且现代前端框架中react和vue均不同程度的使用了虚拟dom的技术,因此通过一个简单的 库赖学习虚拟dom技术就十分必要了,至于为什么会选择snabbdom.js这个库呢?原因主要有两个:

    源码简短,总体代码行数不超过500行。
    著名的vue的虚拟dom实现也是参考了snabbdom.js的实现。

    • 用snabbdomjs 实现上述例子
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.min.js"></script>

</head>
<body>
    <div id="container"></div>
    <button id="btn-change">修改数据</button>
  
    <script type="text/javascript">
        var snabbdom = window.snabbdom
 
        //定义patch
        var patch = snabbdom.init([
           snabbdom_class,
           snabbdom_props,
           snabbdom_style,
           snabbdom_eventlisteners
        ])
 
        var h = snabbdom.h
        var container = document.getElementById("container")
        //生成vnode
        var vnode= h('ul#list',{},[
            h('li.item',{},'Item 1'),
            h('li.item',{},'Item 2')
        ])
 
        patch(container,vnode)
 
        document.getElementById("btn-change").addEventListener('click',function(){
            console.log("111")
            var newVnode =  h('ul#list',{},[
            h('li.item',{},'Item 1'),
            h('li.item',{},'Item B'),
            h('li.item',{},'Item 3')
            ])

           patch(vnode,newVnode)
        })
     </script>
</body>
</html>

//修改数据只是修改了 第二个item 第三,第一个数据没变化(F12查看Element 第一个item没有闪烁)

diff算法

  • 什么是diff算法
  • 去繁就简
  • vdom 为何用diff算法
  • diff算法的实现流程

diff命令是linux系统自带的基础命令
git diff 判断文本文件哪里被修改了
diff算法一直都在,并不是因为react、vue才出现的

vdom为何使用diff算法

  • DOM 操作是昂贵的,因此尽量减少DOM操作
  • 找出本次DOM必须更新的节点来更新,其他的不更新
  • 这个找出的过程,就需要diff算法

diff实现过程

只需要明白

  • path(container,vnode)
  • path(vnode,newnode)

通过VNode创建一个真实的DOM的流程

function createElement(vnode){
   var tag= vnode.tag
   var attrs = vnode.attrs||{}
   var children = vnode.children || []
   if(!tag){
       return null
   }
   var elem = document.createElement(tag)
    var attrName 
    for(attrName in attrs){
     if(attrs.hasOwnProperty(attrName)){
        elem.setAttribute(attrName,attrs[attrName])
      }
    }

    children.forEach(childNode => {
        elem.appendChild(createElement(childNode))
    });
    //返回真实的Dom
    return elem
}

path(vnode,newVnode) 的实现,

function updateChildren(vnode,newVnode){
     var children = vnode.children || []
     var newChildren = newVnode.children || []
    
     //遍历现有的children
     children.forEach((child,index )=> {
         var newChild = newChildren[index]
         if(newChild == null){
            return
         }

         if(child.tag === newChild.tag){
              updateChildren(child,newChild)
         }else{
             replaceNode(child,newChild)
         }
     });
}

function replaceNode(vnode,newVnode){
    var elem = vnode.elem
    var newElem = createElement(newVnode)
}

不仅仅是以上的内容,还有以下的内容

  • 节点新增和删除
  • 节点重新排序
  • 节点属性、样式、事件绑定

dom

相关推荐