我们知道 Vue 利用的是虚拟 DOM 去淘汰对真实 DOM 的操纵次数,来晋升页面运行的效率。本日我们来看看当页面的数据改变的时候,Vue 是如何来更新 DOM 的。Vue和React在更新dom时,利用的算法基内情同,都是基于 snabbdom。 当页面上的数据产生变革时,Vue 不会当即渲染。而是颠末 diff 算法,判定出哪些是不需要变革的,哪些是需要变革更新的,只需要更新那些需要更新的 DOM 就可以了,这样就淘汰了许多不须要的 DOM 操纵,大大晋升了机能。 Vue就利用了这样的抽象节点VNode,它是对真实DOM的一层抽象,而不依赖某个平台,它可以是欣赏器平台,也可以是weex,甚至是node平台也可以对这样一棵抽象DOM树举办建设删除修改等操纵,这也为前后端同构提供了大概。
Vue 更新视图我们知道在 Vue 1.x 中,每一个数据都对应一个 Watcher;而在 Vue 2.x 中,一个组件对应一个 Watcher,这样当我们的数据改变的时候,在 set 函数中会触发 Dep 的 notify 函数去通知 Watcher 去执行 vm._update(vm._render(), hydrating) 要领去更新视图,下面我们来看看 _update 要领
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode const restoreActiveInstance = setActiveInstance(vm) vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. /*基于后端渲染Vue.prototype.__patch__被用来作为一个进口*/ if (!prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } restoreActiveInstance() // update __vue__ reference /*更新新的实例工具的__vue__*/ 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 } // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }
很明明,我们能看到 _update 要了解将传入的 Vnode 将老的 Vnode 举办 patch 操纵。 下面我们再来看看在 patch 函数中都产生了什么。
patchpatch 函数将新老两个节点举办较量,然后判定出哪些是需要修改的节点,只需要修改这些节点即可,这样可以较量高效地更新 DOM,我们先来看一下代码
return function patch (oldVnode, vnode, hydrating, removeOnly) { /*vnode不存在挪用销毁钩子删除节点*/ if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] /*oldVnode不存在,直接建设新节点*/ if (isUndef(oldVnode)) { // empty mount (likely as component), create new root element isInitialPatch = true createElm(vnode, insertedVnodeQueue) } else { /*标志旧的VNode是否有nodeType*/ const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node /*是同一个节点的时候直接修改现有的节点*/ patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) } else { if (isRealElement) { // mounting to a real element // check if this is server-rendered content and if we can perform // a successful hydration. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { /*当旧的VNode是处事端渲染的元素,hydrating记为true*/ oldVnode.removeAttribute(SSR_ATTR) hydrating = true } if (isTrue(hydrating)) { /*需要归并到真实Dom上*/ if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { /*挪用insert钩子*/ invokeInsertHook(vnode, insertedVnodeQueue, true) return oldVnode } else if (process.env.NODE_ENV !== 'production') { warn( 'The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. This is likely caused by incorrect ' + 'HTML markup, for example nesting block-level elements inside ' + '<p>, or missing <tbody>. Bailing hydration and performing ' + 'full client-side render.' ) } } // either not server-rendered, or hydration failed. // create an empty node and replace it /*假如不是处事端渲染可能归并到真实Dom失败,则建设一个空的VNode节点替换它*/ oldVnode = emptyNodeAt(oldVnode) } // replacing existing element /*代替现有元素*/ const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) // create new node createElm( vnode, insertedVnodeQueue, // extremely rare edge case: do not insert if old element is in a // leaving transition. Only happens when combining transition + // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) // update parent placeholder node element, recursively if (isDef(vnode.parent)) { /*组件根节点被替换,遍历更新父节点element*/ let ancestor = vnode.parent const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } ancestor.elm = vnode.elm if (patchable) { /*挪用create回调*/ for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } // #6513 // invoke insert hooks that may have been merged by create hooks. // e.g. for directives that uses the "inserted" hook. const insert = ancestor.data.hook.insert if (insert.merged) { // start at index 1 to avoid re-invoking component mounted hook for (let i = 1; i < insert.fns.length; i++) { insert.fns[i]() } } } else { registerRef(ancestor) } ancestor = ancestor.parent } } // destroy old node if (isDef(parentElm)) { /*移除老节点*/ removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { /*挪用destroy钩子*/ invokeDestroyHook(oldVnode) } } } /*挪用insert钩子*/ invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }