VDOM VS diff算法

VDOM VS diff算法

1. 虚拟DOM( VDOM ) 和 diff算法

Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。
请仔细看这行代码:

return createElement('h1', this.blogTitle)createElement

到底会返回什么呢?
其实不是一个实际的 DOM 元素。它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为**“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。**

1.为什么需要虚拟DOM

DOM是很慢的,其元素非常庞大,页面的性能问题鲜有由JS引起的,大部分都是由DOM操作引起的。如果对前端工作进行抽象的话,主要就是维护状态和更新视图;而更新视图和维护状态都需要DOM操作。其实近年来,前端的框架主要发展方向就是解放DOM操作的复杂性。在jQuery出现以前,我们直接操作DOM结构,这种方法复杂度高,兼容性也较差;有了jQuery强大的选择器以及高度封装的API,我们可以更方便的操作DOM,jQuery帮我们处理兼容性问题,同时也使DOM操作变得简单;但是聪明的程序员不可能满足于此,各种MVVM框架应运而生,有angularJS、avalon、vue.js等,MVVM使用数据双向绑定,使得我们完全不需要操作DOM了,更新了状态视图会自动更新,更新了视图数据状态也会自动更新,可以说MMVM使得前端的开发效率大幅提升,但是其大量的事件绑定使得其在复杂场景下的执行性能堪忧;有没有一种兼顾开发效率和执行效率的方案呢?ReactJS就是一种不错的方案,虽然其将JS代码和HTML代码混合在一起的设计有不少争议,但是其引入的Virtual DOM(虚拟DOM)却是得到大家的一致认同的。

2.理解虚拟DOM

虚拟的DOM的核心思想是:对复杂的文档DOM结构,提供一种方便的工具,进行最小化地DOM操作。这句话,也许过于抽象,却基本概况了虚拟DOM的设计思想

  • (1) 提供一种方便的工具,使得开发效率得到保证
  • (2) 保证最小化的DOM操作,使得执行效率得到保证

总结:

1. 虚拟DOM 是什么?

(虚拟DOM是利用 了js的对象的Object的对象模型来模拟真实DOM, 那么它的结构是一个树形结构.)

虚拟DOM是干什么的?
这就要从浏览器本身讲起如我们所知,在浏览器渲染网页的过程中,加载到HTML文档后,会将文档解析并构建DOM树,然后将其与解析CSS生成的CSSOM树一起结合产生爱的结晶——RenderObject树,然后将RenderObject树渲染成页面(当然中间可能会有一些优化,比如RenderLayer树)。这些过程都存在与渲染引擎之中,渲染引擎在浏览器中是于JavaScript引擎(JavaScriptCore也好V8也好 分离开的,但为了方便JS操作DOM结构,渲染引擎会暴露一些接口供JavaScript调用。由于这两块相互分离,通信是需要付出代价的,因此JavaScript调用DOM提供的接口性能不咋地。各种性能优化的最佳实践也都在尽可能的减少DOM操作次数。

虚拟DOM干了什么?
它直接用JavaScript实现了DOM树(大致上)。组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScript DOM结构,React又通过在这个虚拟DOM上实现了一个 diff 算法找出最小变更,再把这些变更写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,而且由于减少了实际DOM操作次数,性能会有较大提升.

来一波关于虚拟DOM的代码示例
<body>
  <div id="app">
    <header class="header"> 头部 </header>
    <section class="content"> 内容 </section>
    <footer> 底部 </footer>
    <section> 内容 </section>
    <footer> 底部 </footer>
  </div>
</body>
<script>
  var vdom = {
    vnode: {
      tag: 'div',
      attr: {
        idName: '#app'
      },
      content: [
        {
          tag: 'header',
          content: [
            '头部'
          ]
        },
        {
          tag: 'section',
          content: [
            '内容'
          ]
        },
        {
          tag: 'footer',
          content: [
            '底部'
          ]
        }
      ]
    }
  }
  function render(parentNode,vnode,id,className){
    var app = document.createElement('DIV')
    app.id = 'app'
    parentNode.appendChild(app)
  }
  
  // render----------------
  vdom.vnode.content[0].className = 'header'
  vdom = {
    vnode: {
      tag: 'div',
      attr: {
        idName: '#app'
      },
      content: [
        {
          tag: 'header',
          attr: {
            className: 'header'
          },
          content: [
            '头部'
          ]
        },
        {
          tag: 'section',
          content: [
            '内容'
          ]
        },
        {
          tag: 'footer',
          content: [
            '底部'
          ]
        }
      ]
    }
  }
  vdom.vnode.content[1].className = 'content'
  vdom = {
    vnode: {
      tag: 'div',
      attr: {
        idName: '#app'
      },
      content: [
        {
          tag: 'header',
          attr: {
            className: 'header'
          },
          content: [
            '头部'
          ]
        },
        {
          tag: 'section',
          content: [
            '内容'
          ]
        },
        {
          tag: 'footer',
          content: [
            '底部'
          ]
        }
      ]
    }
  }
// 。。。 经过多次的VDOM操作之后
// 通过render函数进行渲染
// 通过diff算法, 将所有的vdom对比一边, 找出不同的地方, 然后进行render函数渲染
var obj = {
  className: 'yyb'
}
var obj1 = {
  className: 'zhangsan'
}
for ( var i in obj ){
  if( obj1[i] ){
    //如果有
    if( obj[i] === obj1[i]){
      // 值是一样的
    }else{
      // 值不一样
    }
  }else{
    //没有属性
  }
}
// 有的
</script>
</html>

2. diff算法(diff算法作为 Virtual DOM的加速器,其算法的改进优化是React整个界面渲染的基础和性能的保障)

diff算法

diff流程图

当数据发生改变时,set方法会让调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁,更新相应的视图。
diff流程图

总结

  • diff算法是用来比较两个或是多个文件, 返回值是文件的不同点
  • 在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。
  • diff算法是同级比较的
  • diff思维也是来自后端
  • diff算法的比较思维
patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
        api.setTextContent(el, vnode.text)
    }else {
        updateEle(el, vnode, oldVnode)
        if (oldCh && ch && oldCh !== ch) {
            updateChildren(el, oldCh, ch)
        }else if (ch){
            createEle(vnode) //create el's children dom
        }else if (oldCh){
            api.removeChildren(el)
        }
    }
}

比较后会出现四种情况:
1、此节点是否被移除 -> 添加新的节点
2、属性是否被改变 -> 旧属性改为新属性
3、文本内容被改变-> 旧内容改为新内容
4、节点要被整个替换 -> 结构完全不相同 移除整个替换
(我在想这算是一个缺点吗?相同子节点不能重复利用了…)

  1. 整个VDOM的使用流程(Vue)
  • 创建VDOM树

  • 利用render函数渲染页面
    (1. 用JavaScript模拟DOM树并渲染这个DOM树)

  • 数据改变,生成新的vDOM
    ** (2. 比较新老DOM树得到比较的差异对象)**

  • 通过diff算法比较 新 旧 两个VDOM , 将不同的地方进行修改, 相同的地方就地复用 , 最后在通过render函数渲染页面
    (3. 把差异对象应用到渲染的DOM树。)
    VDOM的使用流程

猜你喜欢

转载自blog.csdn.net/xuwei1215225/article/details/89414863