vue2從數(shù)據(jù)變化到視圖變化之diff算法圖文詳解
引言
vue
數(shù)據(jù)的渲染會(huì)引入視圖的重新渲染。
從數(shù)據(jù)到視圖的渲染流程可以移步http://www.dbjr.com.cn/article/261839.htm,那么從數(shù)據(jù)的變化到視圖的變化是怎樣的?
vue
在數(shù)據(jù)的初始化階段會(huì)進(jìn)行響應(yīng)式的處理defineReactive
:
/** * Define a reactive property on an Object. */ export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter if (getter && !setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }
數(shù)據(jù)的變化會(huì)觸發(fā)set
方法,會(huì)讓發(fā)布者dep
執(zhí)行 dep.notify
,當(dāng)vue
所有的同步執(zhí)行完后,在異步隊(duì)列中按次序執(zhí)行到vm
的渲染流程,訂閱者接收到發(fā)布者的通知后會(huì)執(zhí)行到this.get()
,指的是
updateComponent = () => { vm._update(vm._render(), hydrating) }
vm._render()
獲取到vNode
后,會(huì)執(zhí)行vm._update
視圖的渲染:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { // ... const prevVnode = vm._vnode // ... if (!prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } // ... }
主要區(qū)別在于數(shù)據(jù)變化引起的視圖變化有prevVnode
,vm.__patch__(prevVnode, vnode)
之后會(huì)執(zhí)行到patch
方法:
function patch (oldVnode, vnode, hydrating, removeOnly) { // ... if (isUndef(oldVnode)) { // empty mount (likely as component), create new root element isInitialPatch = true createElm(vnode, insertedVnodeQueue) } else { const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) } else { // ... // 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) ) // ... // destroy old node if (isDef(parentElm)) { removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }
在數(shù)據(jù)變化引起的patch
過(guò)程中isRealElement
顯然為false
,新舊節(jié)點(diǎn)是否相同的另一個(gè)判斷條件是sameVnode
:
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) ) || ( isTrue(a.isAsyncPlaceholder) && a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error) ) ) ) }
如果sameVnode(oldVnode, vnode)
為false
,則執(zhí)行createElm
以及后續(xù)流程,該流程可以參考模板渲染的流程(請(qǐng)移步http://www.dbjr.com.cn/article/261850.htm )。
sameVnode(oldVnode, vnode)
為true
的時(shí)候,執(zhí)行到patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
:
function patchVnode ( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) { // ... const oldCh = oldVnode.children const ch = vnode.children if (isDef(data) && isPatchable(vnode)) { 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) } if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } else if (isDef(ch)) { if (process.env.NODE_ENV !== 'production') { checkDuplicateKeys(ch) } if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { removeVnodes(oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, '') } } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } }
ch = vnode.children
和oldCh = oldVnode.children
分別獲取到新舊vnode
的子元素,ch
和oldCh
都存在時(shí)會(huì)執(zhí)行到updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
:
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { 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, vnodeToMove, refElm // removeOnly is a special flag used only by <transition-group> // to ensure removed elements stay in correct relative positions // during leaving transitions const canMove = !removeOnly if (process.env.NODE_ENV !== 'production') { checkDuplicateKeys(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, newCh, newStartIdx) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) 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, newCh, newStartIdx) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) if (isUndef(idxInOld)) { // New element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { vnodeToMove = oldCh[idxInOld] if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) oldCh[idxInOld] = undefined canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { // same key but different element. treat as new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } } newStartVnode = newCh[++newStartIdx] } } if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { removeVnodes(oldCh, oldStartIdx, oldEndIdx) } }
這里定義了四個(gè)索引oldStartIdx
、newStartIdx
、oldEndIdx
和newEndIdx
,也可以稱之為指針,通過(guò)while
循環(huán),進(jìn)行四個(gè)指針的移動(dòng):
1、isUndef(oldStartVnode)
如果oldStartVnode
不存在,執(zhí)行oldStartVnode = oldCh[++oldStartIdx]
,將oldStartIdx
指針向右移動(dòng)一位,進(jìn)行下次循環(huán)。
2、isUndef(oldEndVnode)
如果oldEndVnode
不存在,執(zhí)行oldEndVnode = oldCh[--oldEndIdx]
,將oldEndIdx
指針向左移動(dòng)一位,進(jìn)行下次循環(huán)。
3、sameVnode(oldStartVnode, newStartVnode)
如果滿足sameVnode(oldStartVnode, newStartVnode)
,執(zhí)行patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
開始遞歸執(zhí)行,結(jié)束后oldStartIdx
和newStartIdx
分別向右移動(dòng)一位。
4、sameVnode(oldEndVnode, newEndVnode)
如果滿足sameVnode(oldEndVnode, newEndVnode)
,執(zhí)行patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newStartIdx)
開始遞歸執(zhí)行,結(jié)束后oldEndIdx
和newEndIdx
分別向左移動(dòng)一位。
5、sameVnode(oldStartVnode, newEndVnode)
如果滿足sameVnode(oldStartVnode, newEndVnode)
,執(zhí)行patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newStartIdx)
開始遞歸執(zhí)行,結(jié)束后oldStartVnode
向右移動(dòng)一位,newEndIdx
向左移動(dòng)一位。
并且通過(guò)nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
的方式將oldStartVnode.elm
插入到oldEndVnode.elm
節(jié)點(diǎn)之后。
6、sameVnode(oldEndVnode, newStartVnode)
如果滿足sameVnode(oldEndVnode, newStartVnode)
,執(zhí)行patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
開始遞歸執(zhí)行,結(jié)束后newStartIdx
向右移動(dòng)一位,oldEndIdx
向左移動(dòng)一位。
并且通過(guò)nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
的方式將 oldEndVnode.elm
插入到oldStartVnode.elm
節(jié)點(diǎn)之前。
7、如果以上都不滿足
如果新舊vNode
首首、首尾、尾首和尾尾對(duì)比都沒(méi)找到相同的,則在舊vNode
的oldStartIdx
和oldEndIdx
之間去找。 oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
創(chuàng)建以舊vNode
的key為key
值,位置索引為value
的map映射:
function createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key if (isDef(key)) map[key] = i } return map }
如果通過(guò)createKeyToOldIdx
找不到,則通過(guò)findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
和舊vNode
的方式去進(jìn)行比對(duì),并返回位置索引:
function findIdxInOld (node, oldCh, start, end) { for (let i = start; i < end; i++) { const c = oldCh[i] if (isDef(c) && sameVnode(node, c)) return i } }
通過(guò)oldKeyToIdx[newStartVnode.key]
和findIdxInOld (node, oldCh, start, end)
的查詢會(huì)有兩種結(jié)果:
1、沒(méi)找到如果沒(méi)有找到,則以newStartVnode
為渲染vNode
通過(guò)createElm
去進(jìn)行節(jié)點(diǎn)的創(chuàng)建。
2、找到了如果找到了,通過(guò)vnodeToMove = oldCh[idxInOld]
獲取到介于oldStartIdx
和oldEndIdx
之間的可以比對(duì)的vnode
, 執(zhí)行完patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
后將當(dāng)前位置的oldCh[idxInOld] = undefined
。
通過(guò)nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
將vnodeToMove.elm
移動(dòng)到oldStartVnode.elm
之前。
小結(jié)
diff算法從兩端進(jìn)行比對(duì),找不到再?gòu)闹虚g尋找,是一種 “滑動(dòng)窗口” 算法的使用,以達(dá)到通過(guò)節(jié)點(diǎn)移動(dòng)來(lái)實(shí)現(xiàn)原地復(fù)用的目的。
以上就是vue2從數(shù)據(jù)變化到視圖變化之diff算法圖文詳解的詳細(xì)內(nèi)容,更多關(guān)于vue2數(shù)據(jù)視圖變化diff算法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
把vue-router和express項(xiàng)目部署到服務(wù)器的方法
下面小編就為大家分享一篇把vue-router和express項(xiàng)目部署到服務(wù)器的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02jenkins自動(dòng)構(gòu)建發(fā)布vue項(xiàng)目的方法步驟
這篇文章主要介紹了jenkins自動(dòng)構(gòu)建發(fā)布vue項(xiàng)目的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Vite項(xiàng)目搭建與環(huán)境配置的完整版教程
Vite?使用?Rollup?作為默認(rèn)的構(gòu)建工具,可以將應(yīng)用程序的源代碼打包成一個(gè)或多個(gè)優(yōu)化的靜態(tài)文件,本文就來(lái)為大家介紹一下Vite如何進(jìn)行項(xiàng)目搭建與環(huán)境配置吧2023-09-09vue-router動(dòng)態(tài)設(shè)置頁(yè)面title的實(shí)例講解
今天小編就為大家分享一篇vue-router動(dòng)態(tài)設(shè)置頁(yè)面title的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08Vue中的異步組件函數(shù)實(shí)現(xiàn)代碼
這篇文章主要介紹了Vue中的異步組件函數(shù)實(shí)現(xiàn)代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-07-07Vue-cli3項(xiàng)目引入Typescript的實(shí)現(xiàn)方法
這篇文章主要介紹了Vue-cli3項(xiàng)目引入Typescript的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10axios向后臺(tái)傳遞數(shù)組作為參數(shù)的方法
今天小編就為大家分享一篇axios向后臺(tái)傳遞數(shù)組作為參數(shù)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08Vue向后臺(tái)傳數(shù)組數(shù)據(jù),springboot接收vue傳的數(shù)組數(shù)據(jù)實(shí)例
這篇文章主要介紹了Vue向后臺(tái)傳數(shù)組數(shù)據(jù),springboot接收vue傳的數(shù)組數(shù)據(jù)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11vue-image-crop基于Vue的移動(dòng)端圖片裁剪組件示例
這篇文章主要介紹了vue-image-crop基于Vue的移動(dòng)端圖片裁剪組件示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08