dom的原理简析(2)

在这个方法当中最为关键的就是vm.__patch__方法,这也是整个virtaul-dom当中最为核心的方法,主要完成了prevVnode和vnode的diff过程并根据需要操作的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) } } }

在对oldVnode和vnode类型判断中有个sameVnode方法,这个方法决定了是否需要对oldVnode和vnode进行diff及patch的过程。

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> <p>this is demo</p> </div>

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

diff的过程主要是通过调用patchVnode方法进行的:

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节点进行了预处理的操作

接下来:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wyfwyp.html