Vuevue原理

Vue 2.0 patch 原理分析

2017-10-02  本文已影响866人  fehysunny

本文基于vue-2.4.4源码进行分析

Vue 2.0开始,引入VirtualDOM

使用VirtualDOM而不使用真实DOM是出于性能优化的考虑。

真实DOM使用document.createElement创建DOM元素,但是这个方法会带来性能上的损失。

举个例子:

let div = document.createElement('div');
let count = 0
for(let k in div) {
    count++
}
console.log(count)  // 231

执行上面的代码,我们可以看到该方法创建的DOM元素的属性多达231个,但是我们真正需要的可能只有不到10%。

为了解决这个问题,VirtualDOM应运而生。它和真实DOM保持映射关系,每个VNode节点都存储了对应真实DOM节点的一些重要参数,当数据发生改变时,在改变真实DOM节点之前,会先比较相应的VNode的的数据,如果需要改变,才更新真实DOM。这样就可以通过操作VirtualDOM来提高直接操作DOM的效率和性能。

比较VNode数据这个操作就是我们今天要讨论的patch,在讨论之前,我们先简单说下VNode

VNode

在上篇Vue 2.0 模板编译源码分析中我们得出模板编译的结果是render function

render function的运行结果就是VNode, 参考src/core/instance/render.js

Vue.prototype._render = function (): VNode {
  ...
  const {
    render,
    staticRenderFns,
    _parentVnode
  } = vm.$options
  ... 
  vnode = render.call(vm._renderProxy, vm.$createElement)
  ...
}   

Vue 2.0中的VNode(src/core/vdom/vnode.js)定义如下:

export default class VNode {
constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag    // 元素标签
    this.data = data    // 属性
    this.children = children    // 子元素列表
    this.text = text
    this.elm = elm    //  对应的真实 DOM 元素
    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
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }
}

它是真实DOM的简化版,与真实DOM一一对映。通过new实例化的VNode可以分为:EmptyVNode(注释节点)、TextVNode(文本节点)、ElementVNode(元素节点)、ComponentVNode(组件节点)、CloneVNode(克隆节点)等。

patch原理

再拉通一下整个思路,目前我们晓得

render function 生成 VNode,是在 vm._render 里完成的。

那么vm._render方法又是在什么时候调用的呢?

debugger一下代码,可以看到流程如下:

初始化时,通过render function 生成 VNode的同时进行Watcher的绑定。当数据发生会变化时,会执行_update方法,生成一个新的VNode对象,然后调用__patch__方法,比较新生成的VNode和旧的VNode,最后将差异(变化的节点)更新到真实的DOM树上。

patch(src/core/vdom/patch.js)所用的diff算法来源于snabbdom,只会在同层级进行比较,不会跨层级比较。图示如下:

diff algorithm (by Christopher Chedeau)

下面结合源码进行原理分析:

入参

patch方法接收6个参数:

function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
  ...
}

流程

  1. 如果vnode不存在,但是oldVnode存在,说明是需要销毁旧节点,则调用invokeDestroyHook(oldVnode)来销毁oldVnode

    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }
    
  2. 如果vnode存在,但是oldVnode不存在,说明是需要创建新节点,则调用createElm来创建新节点。

    if (isUndef(oldVnode)) {
     isInitialPatch = true  // 用于做延迟插值处理
     createElm(vnode, insertedVnodeQueue, parentElm, refElm)
    }   
    
  3. vnodeoldVnode都存在时

  1. 最后返回 vnode.elm

原理

由上面的流程我们知道了当vnodeoldVnode都存在、oldVnode不是真实节点,并且vnodeoldVnode是同一节点时,才会调用patchVnode进行patch

下面根据patchVnode源码分析patch的原理:

  1. 如果oldVnodevnode完全一致,则可认为没有变化,return
  2. 如果oldVnodeisAsyncPlaceholder属性为true时,跳过检查异步组件,return
  3. 如果oldVnodevnode都是静态节点,且具有相同的key,并且当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elmoldVnode.child都复制到vnode上,也不用再有其他操作,return
  4. 否则,如果vnode不是文本节点时
  1. 如果vnode是文本节点但是vnode.text != oldVnode.text时只需要更新vnode.elm的文本内容就可以。

原理流程图如下:

自此,Vue的patch原理就分析完了。

上一篇 下一篇

猜你喜欢

热点阅读