精华内容
下载资源
问答
  • vue 虚拟dom实现原理

    万次阅读 多人点赞 2017-12-14 10:03:33
    Vue版本: 2.3.2 virtual-dom(后文简称vdom)的概念大规模的推广还是得益于react出现,virtual-dom也是react这个框架的非常重要的特性之一。相比于频繁的手动去操作dom而带来性能问题,vdom很好的将dom做了一层映射...

    Vue版本: 2.3.2

    virtual-dom(后文简称vdom)的概念大规模的推广还是得益于react出现,virtual-dom也是react这个框架的非常重要的特性之一。相比于频繁的手动去操作dom而带来性能问题,vdom很好的将dom做了一层映射关系,进而将在我们本需要直接进行dom的一系列操作,映射到了操作vdom,而vdom上定义了关于真实dom的一些关键的信息,vdom完全是用js去实现,和宿主浏览器没有任何联系,此外得益于js的执行速度,将原本需要在真实dom进行的创建节点,删除节点,添加节点等一系列复杂的dom操作全部放到vdom中进行,这样就通过操作vdom来提高直接操作的dom的效率和性能。

    Vue2.0版本也引入了vdom。其vdom算法是基于snabbdom算法所做的修改。

    Vue的整个应用生命周期当中,每次需要更新视图的时候便会使用vdom。那么在Vue当中,vdom是如何和Vue这个框架融合在一起工作的呢?以及大家常常提到的vdomdiff算法又是怎样的呢?接下来就通过这篇文章简单的向大家介绍下Vue当中的vdom是如何去工作的。

    首先,我们还是来看下Vue生命周期当中初始化的最后阶段:将vm实例挂载到dom上,源码在src/core/instance/init.js

        Vue.prototype._init = function () {
            ...
            vm.$mount(vm.$options.el)  
            ...
        }   

    实际上是调用了src/core/instance/lifecycle.js中的mountComponent方法,
    mountComponent函数的定义是:

    export function mountComponent (
      vm: Component,
      el: ?Element,
      hydrating?: boolean
    ): Component {
      // vm.$el为真实的node
      vm.$el = el
      // 如果vm上没有挂载render函数
      if (!vm.$options.render) {
        // 空节点
        vm.$options.render = createEmptyVNode
      }
      // 钩子函数
      callHook(vm, 'beforeMount')
    
      let updateComponent
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        ...
      } else {
        // updateComponent为监听函数, new Watcher(vm, updateComponent, noop)
        updateComponent = () => {
          // Vue.prototype._render 渲染函数
          // vm._render() 返回一个VNode
          // 更新dom
          // vm._render()调用render函数,会返回一个VNode,在生成VNode的过程中,会动态计算getter,同时推入到dep里面
          vm._update(vm._render(), hydrating)
        }
      }
    
      // 新建一个_watcher对象
      // vm实例上挂载的_watcher主要是为了更新DOM
      // vm/expression/cb
      vm._watcher = new Watcher(vm, updateComponent, noop)
      hydrating = false
    
      // manually mounted instance, call mounted on self
      // mounted is called for render-created child components in its inserted hook
      if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted')
      }
      return vm
    }

    注意上面的代码中定义了一个updateComponent函数,这个函数执行的时候内部会调用vm._update(vm._render(), hyddrating)方法,其中vm._render方法会返回一个新的vnode,(关于vm_render是如何生成vnode的建议大家看看vue的关于compile阶段的代码),然后传入vm._update方法后,就用这个新的vnode和老的vnode进行diff,最后完成dom的更新工作。那么updateComponent都是在什么时候去进行调用呢?

    vm._watcher = new Watcher(vm, updateComponent, noop)

    实例化一个watcher,在求值的过程中this.value = this.lazy ? undefined : this.get(),会调用this.get()方法,因此在实例化的过程当中Dep.target会被设为这个watcher,通过调用vm._render()方法生成新的Vnode并进行diff的过程中完成了模板当中变量依赖收集工作。即这个watcher被添加到了在模板当中所绑定变量的依赖当中。一旦model中的响应式的数据发生了变化,这些响应式的数据所维护的dep数组便会调用dep.notify()方法完成所有依赖遍历执行的工作,这里面就包括了视图的更新即updateComponent方法,它是在mountComponent中的定义的。

    updateComponent方法的定义是:

    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }

    完成视图的更新工作事实上就是调用了vm._update方法,这个方法接收的第一个参数是刚生成的Vnode,调用的vm._update方法(src/core/instance/lifecycle.js)的定义是

    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
        const vm: Component = this
        if (vm._isMounted) {
          callHook(vm, 'beforeUpdate')
        }
        const prevEl = vm.$el
        const prevVnode = vm._vnode
        const prevActiveInstance = activeInstance
        activeInstance = vm
        // 新的vnode
        vm._vnode = vnode
        // Vue.prototype.__patch__ is injected in entry points
        // based on the rendering backend used.
        // 如果需要diff的prevVnode不存在,那么就用新的vnode创建一个真实dom节点
        if (!prevVnode) {
          // initial render
          // 第一个参数为真实的node节点
          vm.$el = vm.__patch__(
            vm.$el, vnode, hydrating, false /* removeOnly */,
            vm.$options._parentElm,
            vm.$options._refElm
          )
        } else {
          // updates
          // 如果需要diff的prevVnode存在,那么首先对prevVnode和vnode进行diff,并将需要的更新的dom操作已patch的形式打到prevVnode上,并完成真实dom的更新工作
          vm.$el = vm.__patch__(prevVnode, vnode)
        }
        activeInstance = prevActiveInstance
        // update __vue__ reference
        if (prevEl) {
          prevEl.__vue__ = null
        }
        if (vm.$el) {
          vm.$el.__vue__ = vm
        }
        // if parent is an HOC, update its $el as well
        if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
          vm.$parent.$el = vm.$el
        }
    }

    在这个方法当中最为关键的就是vm.__patch__方法,这也是整个virtaul-dom当中最为核心的方法,主要完成了prevVnodevnodediff过程并根据需要操作的vdom节点打patch,最后生成新的真实dom节点并完成视图的更新工作。

    接下来就让我们看下vm.__patch__里面到底发生了什么:

        function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
            // 当oldVnode不存在时
            if (isUndef(oldVnode)) {
                // 创建新的节点
                createElm(vnode, insertedVnodeQueue, parentElm, refElm)
            } else {
                const isRealElement = isDef(oldVnode.nodeType)
                if (!isRealElement && sameVnode(oldVnode, vnode)) {
                // patch existing root node
                // 对oldVnode和vnode进行diff,并对oldVnode打patch
                patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
          } 
            }
        }

    在对oldVnodevnode类型判断中有个sameVnode方法,这个方法决定了是否需要对oldVnodevnode进行diffpatch的过程。

    function sameVnode (a, b) {
      return (
        a.key === b.key &&
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      )
    }

    sameVnode会对传入的2个vnode进行基本属性的比较,只有当基本属性相同的情况下才认为这个2个vnode只是局部发生了更新,然后才会对这2个vnode进行diff,如果2个vnode的基本属性存在不一致的情况,那么就会直接跳过diff的过程,进而依据vnode新建一个真实的dom,同时删除老的dom节点。

    vnode基本属性的定义可以参见源码:src/vdom/vnode.js里面对于vnode的定义。

    constructor (
        tag?: string,
        data?: VNodeData,         // 关于这个节点的data值,包括attrs,style,hook等
        children?: ?Array<VNode>, // 子vdom节点
        text?: string,        // 文本内容
        elm?: Node,           // 真实的dom节点
        context?: Component,  // 创建这个vdom的上下文
        componentOptions?: VNodeComponentOptions
      ) {
        this.tag = tag
        this.data = data
        this.children = children
        this.text = text
        this.elm = elm
        this.ns = undefined
        this.context = context
        this.functionalContext = undefined
        this.key = data && data.key
        this.componentOptions = componentOptions
        this.componentInstance = undefined
        this.parent = undefined
        this.raw = false
        this.isStatic = false
        this.isRootInsert = true
        this.isComment = false
        this.isCloned = false
        this.isOnce = false
      }
    
      // DEPRECATED: alias for componentInstance for backwards compat.
      /* istanbul ignore next */
      get child (): Component | void {
        return this.componentInstance
      }
    }

    每一个vnode都映射到一个真实的dom节点上。其中几个比较重要的属性:

    • tag 属性即这个vnode的标签属性
    • data 属性包含了最后渲染成真实dom节点后,节点上的class,attribute,style以及绑定的事件
    • children 属性是vnode的子节点
    • text 属性是文本属性
    • elm 属性为这个vnode对应的真实dom节点
    • key 属性是vnode的标记,在diff过程中可以提高diff的效率,后文有讲解

    比如,我定义了一个vnode,它的数据结构是:

        {
            tag: 'div'
            data: {
                id: 'app',
                class: 'page-box'
            },
            children: [
                {
                    tag: 'p',
                    text: 'this is demo'
                }
            ]
        }

    最后渲染出的实际的dom结构就是:

       <div id="app" class="page-box">
           <p>this is demo</p>
       </div>

    让我们再回到patch函数当中,在当oldVnode不存在的时候,这个时候是root节点初始化的过程,因此调用了createElm(vnode, insertedVnodeQueue, parentElm, refElm)方法去创建一个新的节点。而当oldVnodevnodesameVnode(oldVnode, vnode)2个节点的基本属性相同,那么就进入了2个节点的diff过程。

    diff的过程主要是通过调用patchVnode(src/core/vdom/patch.js)方法进行的:

    function patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) {
        ...
    }
    if (isDef(data) && isPatchable(vnode)) {
          // cbs保存了hooks钩子函数: 'create', 'activate', 'update', 'remove', 'destroy'
          // 取出cbs保存的update钩子函数,依次调用,更新attrs/style/class/events/directives/refs等属性
          for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
          if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
        }

    更新真实dom节点的data属性,相当于对dom节点进行了预处理的操作

    接下来:

        ...
        const elm = vnode.elm = oldVnode.elm
        const oldCh = oldVnode.children
        const ch = vnode.children
        // 如果vnode没有文本节点
        if (isUndef(vnode.text)) {
          // 如果oldVnode的children属性存在且vnode的属性也存在
          if (isDef(oldCh) && isDef(ch)) {
            // updateChildren,对子节点进行diff
            if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
          } else if (isDef(ch)) {
            // 如果oldVnode的text存在,那么首先清空text的内容
            if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
            // 然后将vnode的children添加进去
            addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
          } else if (isDef(oldCh)) {
            // 删除elm下的oldchildren
            removeVnodes(elm, oldCh, 0, oldCh.length - 1)
          } else if (isDef(oldVnode.text)) {
            // oldVnode有子节点,而vnode没有,那么就清空这个节点
            nodeOps.setTextContent(elm, '')
          }
        } else if (oldVnode.text !== vnode.text) {
          // 如果oldVnode和vnode文本属性不同,那么直接更新真是dom节点的文本元素
          nodeOps.setTextContent(elm, vnode.text)
        }

    这其中的diff过程中又分了好几种情况,oldCholdVnode的子节点,chVnode的子节点:

    1. 首先进行文本节点的判断,若oldVnode.text !== vnode.text,那么就会直接进行文本节点的替换;
    2. vnode没有文本节点的情况下,进入子节点的diff
    3. oldChch都存在且不相同的情况下,调用updateChildren对子节点进行diff
    4. oldCh不存在,ch存在,首先清空oldVnode的文本节点,同时调用addVnodes方法将ch添加到elm真实dom节点当中;
    5. oldCh存在,ch不存在,则删除elm真实节点下的oldCh子节点;
    6. oldVnode有文本节点,而vnode没有,那么就清空这个文本节点。

    这里着重分析下updateChildren(src/core/vdom/patch.js)方法,它也是整个diff过程中最重要的环节:

    function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
        // 为oldCh和newCh分别建立索引,为之后遍历的依据
        let oldStartIdx = 0
        let newStartIdx = 0
        let oldEndIdx = oldCh.length - 1
        let oldStartVnode = oldCh[0]
        let oldEndVnode = oldCh[oldEndIdx]
        let newEndIdx = newCh.length - 1
        let newStartVnode = newCh[0]
        let newEndVnode = newCh[newEndIdx]
        let oldKeyToIdx, idxInOld, elmToMove, refElm
        
        // 直到oldCh或者newCh被遍历完后跳出循环
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
          if (isUndef(oldStartVnode)) {
            oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
          } else if (isUndef(oldEndVnode)) {
            oldEndVnode = oldCh[--oldEndIdx]
          } else if (sameVnode(oldStartVnode, newStartVnode)) {
            patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
            oldStartVnode = oldCh[++oldStartIdx]
            newStartVnode = newCh[++newStartIdx]
          } else if (sameVnode(oldEndVnode, newEndVnode)) {
            patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
            oldEndVnode = oldCh[--oldEndIdx]
            newEndVnode = newCh[--newEndIdx]
          } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
            patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
            canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
            oldStartVnode = oldCh[++oldStartIdx]
            newEndVnode = newCh[--newEndIdx]
          } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
            patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
            // 插入到老的开始节点的前面
            canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
            oldEndVnode = oldCh[--oldEndIdx]
            newStartVnode = newCh[++newStartIdx]
          } else {
            // 如果以上条件都不满足,那么这个时候开始比较key值,首先建立key和index索引的对应关系
            if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
            idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
            // 如果idxInOld不存在
            // 1. newStartVnode上存在这个key,但是oldKeyToIdx中不存在
            // 2. newStartVnode上并没有设置key属性
            if (isUndef(idxInOld)) { // New element
              // 创建新的dom节点
              // 插入到oldStartVnode.elm前面
              // 参见createElm方法
              createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
              newStartVnode = newCh[++newStartIdx]
            } else {
              elmToMove = oldCh[idxInOld]
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' && !elmToMove) {
                warn(
                  'It seems there are duplicate keys that is causing an update error. ' +
                  'Make sure each v-for item has a unique key.'
                )
              
              // 将找到的key一致的oldVnode再和newStartVnode进行diff
              if (sameVnode(elmToMove, newStartVnode)) {
                patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
                oldCh[idxInOld] = undefined
                // 移动node节点
                canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
                newStartVnode = newCh[++newStartIdx]
              } else {
                // same key but different element. treat as new element
                // 创建新的dom节点
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
                newStartVnode = newCh[++newStartIdx]
              }
            }
          }
        }
        // 如果最后遍历的oldStartIdx大于oldEndIdx的话
        if (oldStartIdx > oldEndIdx) {        // 如果是老的vdom先被遍历完
          refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
          // 添加newVnode中剩余的节点到parentElm中
          addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
        } else if (newStartIdx > newEndIdx) { // 如果是新的vdom先被遍历完,则删除oldVnode里面所有的节点
          // 删除剩余的节点
          removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
        }
    }

    在开始遍历diff前,首先给oldChnewCh分别分配一个startIndexendIndex来作为遍历的索引,当oldCh或者newCh遍历完后(遍历完的条件就是oldCh或者newChstartIndex >= endIndex),就停止oldChnewChdiff过程。接下来通过实例来看下整个diff的过程(节点属性中不带key的情况):

    1. 首先从第一个节点开始比较,不管是oldCh还是newCh的起始或者终止节点都不存在sameVnode,同时节点属性中是不带key标记的,因此第一轮的diff完后,newChstartVnode被添加到oldStartVnode的前面,同时newStartIndex前移一位;
      1

    2. 第二轮的diff中,满足sameVnode(oldStartVnode, newStartVnode),因此对这2个vnode进行diff,最后将patch打到oldStartVnode上,同时oldStartVnodenewStartIndex都向前移动一位
      2

    3. 第三轮的diff中,满足sameVnode(oldEndVnode, newStartVnode),那么首先对oldEndVnodenewStartVnode进行diff,并对oldEndVnode进行patch,并完成oldEndVnode移位的操作,最后newStartIndex前移一位,oldStartVnode后移一位;
      3

    4. 第四轮的diff中,过程同步骤3;
      4

    5. 第五轮的diff中,同过程1;
      5

    6. 遍历的过程结束后,newStartIdx > newEndIdx,说明此时oldCh存在多余的节点,那么最后就需要将这些多余的节点删除。
      6

    vnode不带key的情况下,每一轮的diff过程当中都是起始结束节点进行比较,直到oldCh或者newCh被遍历完。而当为vnode引入key属性后,在每一轮的diff过程中,当起始结束节点都没有找到sameVnode时,首先对oldCh中进行key值与索引的映射:

    if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
    idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null

    createKeyToOldIdx(src/core/vdom/patch.js)方法,用以将oldCh中的key属性作为,而对应的节点的索引作为。然后再判断在newStartVnode的属性中是否有key,且是否在oldKeyToIndx中找到对应的节点。

    1. 如果不存在这个key,那么就将这个newStartVnode作为新的节点创建且插入到原有的root的子节点中:
    if (isUndef(idxInOld)) { // New element
        // 创建新的dom节点
        // 插入到oldStartVnode.elm前面
        // 参见createElm方法
        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
             newStartVnode = newCh[++newStartIdx]
        } 
    1. 如果存在这个key,那么就取出oldCh中的存在这个keyvnode,然后再进行diff的过程:
           elmToMove = oldCh[idxInOld]
              /* istanbul ignore if */
              if (process.env.NODE_ENV !== 'production' && !elmToMove) {
              
              // 将找到的key一致的oldVnode再和newStartVnode进行diff
              if (sameVnode(elmToMove, newStartVnode)) {
                patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
                // 清空这个节点
                oldCh[idxInOld] = undefined
                // 移动node节点
                canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
                newStartVnode = newCh[++newStartIdx]
              } else {
                // same key but different element. treat as new element
                // 创建新的dom节点
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
                newStartVnode = newCh[++newStartIdx]
              }

    通过以上分析,给vdom上添加key属性后,遍历diff的过程中,当起始点结束点搜寻diff出现还是无法匹配的情况下时,就会用key来作为唯一标识,来进行diff,这样就可以提高diff效率。

    带有Key属性的vnodediff过程可见下图:

    注意在第一轮的diff过后oldCh上的B节点被删除了,但是newCh上的B节点elm属性保持对oldChB节点elm引用。
    wechatimg16
    wechatimg17
    wechatimg18
    wechatimg19
    wechatimg20



    转自 https://github.com/DDFE/DDFE-blog/issues/18

    展开全文
  • virtual-dom(后文简称vdom) 在Vue的整个应用生命周期当中,每次需要更新视图的时候便会使用vdom。 组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给...

    virtual-dom(后文简称vdom)

    Vue的整个应用生命周期当中,每次需要更新视图的时候便会使用vdom

     

     

    组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

    展开全文
  • vue 虚拟DOM原理

    2020-10-14 17:29:51
    主要介绍了vue 虚拟DOM原理,帮助大家更好的理解和学习vue,感兴趣的朋友可以了解下
  • vue 虚拟dom实现原理

    千次阅读 2020-03-23 20:23:50
    vue 虚拟dom实现原理前言一、真实DOM和其解析流程二、Virtual DOM 作用是什么?三、虚拟DOM实现 前言 Vue.js 2.0引入Virtual DOM,比Vue.js 1.0的初始渲染速度提升了2-4倍,并大大降低了内存消耗。那么,什么是...

    前言

    Vue.js 2.0引入Virtual DOM,比Vue.js 1.0的初始渲染速度提升了2-4倍,并大大降低了内存消耗。那么,什么是Virtual DOM?为什么需要Virtual DOM?它是通过什么方式去提升页面渲染效率的呢?这是本文所要探讨的问题。

    一、真实DOM和其解析流程

    浏览器渲染引擎工作流程都差不多,大致分为5步,创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting
    第一步,用HTML分析器,分析HTML元素,构建一颗DOM树(标记化和树构建)。

    第二步,用CSS分析器,分析CSS文件和元素上的inline样式,生成页面的样式表。

    第三步,将DOM树和样式表,关联起来,构建一颗Render树(这一过程又称为Attachment)。每个DOM节点都有attach方法,接受样式信息,返回一个render对象(又名renderer)。这些render对象最终会被构建成一颗Render树。

    第四步,有了Render树,浏览器开始布局,为每个Render树上的节点确定一个在显示屏上出现的精确坐标。

    第五步,Render树和节点显示坐标都有了,就调用每个节点paint方法,把它们绘制出来。

    DOM树的构建是文档加载完成开始的?构建DOM数是一个渐进过程,为达到更好用户体验,渲染引擎会尽快将内容显示在屏幕上。它不必等到整个HTML文档解析完毕之后才开始构建render数和布局。

    Render树是DOM树和CSSOM树构建完毕才开始构建的吗?这三个过程在实际进行的时候又不是完全独立,而是会有交叉。会造成一边加载,一遍解析,一遍渲染的工作现象。

    CSS的解析是从右往左逆向解析的(从DOM树的下-上解析比上-下解析效率高),嵌套标签越多,解析越慢。

    二、Virtual DOM 作用是什么?

    虚拟DOM的最终目标是将虚拟节点渲染到视图上。但是如果直接使用虚拟节点覆盖旧节点的话,会有很多不必要的DOM操作。例如,一个ul标签下很多个li标签,其中只有一个li有变化,这种情况下如果使用新的ul去替代旧的ul,因为这些不必要的DOM操作而造成了性能上的浪费。

    为了避免不必要的DOM操作,虚拟DOM在虚拟节点映射到视图的过程中,将虚拟节点与上一次渲染视图所使用的旧虚拟节点(oldVnode)做对比,找出真正需要更新的节点来进行DOM操作,从而避免操作其他无需改动的DOM。

    其实虚拟DOM在Vue.js主要做了两件事:

    提供与真实DOM节点所对应的虚拟节点vnode
    将虚拟节点vnode和旧虚拟节点oldVnode进行对比,然后更新视图

    三、虚拟DOM实现

    其实虚拟DOM就是用js完成本来在html上的元素创建,所以我们本来所编写的组件,也是一种虚拟dom的形式,如template:‘<h1>virtual dom</h1>’本身就是一个虚拟dom实现
    另外一种就是render渲染函数了
    在这里插入图片描述

    展开全文
  • vue虚拟dom实现

    2018-08-27 06:43:13
    vue和react使用虚拟dom原因,以及实现原理 virtual dom 是通过js实现的,将原本复杂的DOM操作全部放到virtual dom 中,执行速度会比真实的DOM操作要快,直接提高了效率和性能。 Virtual DOM 就是用一个原生的 JS ...

    vue和react使用虚拟dom原因,以及实现原理

    virtual dom 是通过js实现的,将原本复杂的DOM操作全部放到virtual dom 中,执行速度会比真实的DOM操作要快,直接提高了效率和性能。

    Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多。在 Vue.js 中,Virtual DOM 是用 VNode 这么一个 Class 去描述

    其实 VNode 是对真实 DOM 的一种抽象描述,它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的。

    Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch等过程。那么在 Vue.js 中,VNode 的 create 是通过之前提到的 createElement 方法创建的

    vue官网说虚拟dom是通过对真实的DOM发生的变化保持追踪

    return createElement()并非真正的返回一个真实的DOM元素,而是告诉Vue页面上需要渲染一个什么节点和子节点,我们把这样的节点成为虚拟节点,虚拟DOM是我们对由Vue组件树建立起的整个vnode树的称呼。

    virtual dom 设计思想:

    • 提供一种方便的工具,使得开发效率得到保证

    • 保证最小化的DOM操作,使得执行效率得到保证

    实现步骤:

    1. 用js表示DOM结构
    2. 比较两个虚拟DOM树的差异
    3. 对真实DOM进行最小量的修改

    vue技术揭秘 参考链接1

    参考链接2

    展开全文
  • vue虚拟dom原理实现

    2020-08-07 17:27:17
    真实dom 浏览器渲染引擎工作流程: 创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting 详细点就是: 第一步,用HTML分析器,分析HTML元素,构建一颗DOM树(标记化和树构建)。 第二步,用...
  • 参考一下两个链接,能充分理解其原理 https://blog.csdn.net/u010692018/article/details/78799335/ https://blog.csdn.net/M6i37JK/article/details/78140159
  • vue虚拟dom原理

    2018-03-26 11:31:00
    Virual DOM是用JS对象...vue的virtual dom的diff算法是基于snabbdom算法改造而来,与react的diff算法一样仅在同级的vnode间做diff,递归的进行同级vnode的diff,最终实现整个DOM树的更新。 虚拟DOM的缺点: 1. 代...
  • vue虚拟dom原理剖析

    2018-12-28 00:24:09
    vue2.0渲染层做了...了解 snabbdom的原理之后再回过头来看 vue虚拟dom结构的实现。就难度不大了! 于是,这里将自己写的 snabbdom 源码解析的一系列文章做一个汇总。 snabbdom源码解析(一) 准备工作 snabb...
  • 虚拟Dom原理 vue 虚拟dom实现原理 Vue面试题总结(长期更新) Vue源码解析 Vue面试题精编 前端最强面经汇总 前端面试题文档 前端实用文档 前端面试题目 面试资源汇总 中级前端面试
  • 虚拟DOM JavaScript对象描述真实DOM 真实DOM的属性很多,创建DOM节点开销很大 虚拟DOM只是普通JavaScript对象,描述属性并不需要很多,创建开销很小 手动操作DOM很难跟踪以前的DOM状态,而虚拟DOM可以跟踪上一次...
  • Vue虚拟DOM原理及面试题(笔记) 面试题:请你阐述一下对vue虚拟dom的理解 什么是虚拟dom? 虚拟dom本质上就是一个普通的JS对象,用于描述视图的界面结构 在vue中,每个组件都有一个render函数,每个render函数...
  • Vue虚拟dom原理(纯文本总结版) 虚拟dom的是一个用来映射真实dom的js对象。我们使用jQuery或者是原生js进行编程的时候,要想修改某一个dom,我们是通过使用jQuery的$选择器或者是js的getElementBy…语法先选取这个...
  • Vue虚拟Dom和Diff原理

    2021-01-08 16:58:53
    1.什么是Virtral DOM? Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM...对于虚拟DOM,咱们来看一个简单的实例,就是下图所示的这个,详细
  • vue虚拟DOM 和 Diff原理

    2020-09-20 20:00:46
    虚拟DOM概念: 用js来模拟DOM中的节点。传说中的虚拟DOM。 class Element { constructor(tag,ats,children){ this.tag = tag; this.ats = ats || {}; this.children = children || {} } render(){ // 这个...
  • 虚拟DOM实现原理

    2021-06-08 08:11:01
    虚拟DOM实现原理 什么是虚拟DOM Virtual Dom(虚拟DOM)是由普通的js对象来描述DOM对象,因为不是真实的DOM对象,因此被称为虚拟DOM 为什么使用虚拟DOM DOM对象成员多,创建dom对象成本高 手动操作dom比较麻烦,要...
  • vue 虚拟domIn this article, we do a dive into the virtual dom in Vue.js 3, and how we can traverse it with the goal of finding a specific component (or what we will call a vnode - more on this soon)....
  • vue虚拟dom实现原理

    千次阅读 2018-11-26 11:21:04
    1,数据和模板相结合生成虚拟dom 2,虚拟dom转化成真是dom渲染到页面上 3,当数据发生改变时,新的数据和模板相结合会生成新的虚拟dom 4,新旧虚拟dom进行比对找差异 5,找到差异后根据差异改变dom 6,老的虚拟...
  • vue虚拟DOM和diff算法,是Vue面试的最后一招,当然也是极其简单了,先说Virtual Dom,来一句概念: 用js来模拟DOM中的节点。传说中的虚拟DOM。 是不是一下子秒懂 没懂再来一张 Virtual Dom就先这样,理解了就...
  • 参考资料: https://zhuanlan.zhihu.com/p/76384873 视频资料:https://www.bilibili.com/video/BV1jJ411x7FX?from=search&seid=9120067746838181964 参考资料:https://zhuanlan.zhihu.com/p/76384873(有个图...
  • vue 虚拟dom和diff算法详解

    千次阅读 多人点赞 2021-02-23 16:23:54
    虚拟dom是当前前端最流行的两个框架(vue和react)都用到的一种技术,都说他能帮助vue和react提升渲染性能,提升用户体验。那么今天我们来详细看看虚拟dom到底是个什么鬼 虚拟dom的定义与作用 什么是虚拟dom ...
  • 前端主流框架 vue 和 react 中都使用了虚拟DOM技术,DOM-DIFF算法也是现在前端面试中的必考题目,那么如何才能真正深刻理解虚拟DOM和DOM-DIFF原理呢?最好的办法莫...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,997
精华内容 3,598
关键字:

vue虚拟dom实现原理

vue 订阅