前端(vue)入門到精通課程:進入學習
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:
以下原始碼來自,會有一些提取,相關函數會附上連結。【相關推薦:、】
diff
過程就是呼叫 patch
函數,比較新舊節點,一邊比較一邊給真實DOM打修補程式,那麼我們就先來看一下patch
函數: return function patch(oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) { //新的節點不存在
if (isDef(oldVnode)) //舊的節點存在
invokeDestroyHook(oldVnode) //銷燬舊節點
return
}
.........
//isRealElement就是為處理初始化定義的,元件初始化的時候,沒有oldVnode,那麼Vue會傳入一個真實dom
if (!isRealElement && sameVnode(oldVnode, vnode)) { -----判斷是否值得去比較
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) ---打修補程式,後面會詳細講
} else {
......
if (isRealElement)
......
oldVnode = emptyNodeAt(oldVnode) //轉化為Vnode,並賦值給oldNode
}
// replacing existing element
const oldElm = oldVnode.elm ----找到oldVnode對應的真實節點
const parentElm = nodeOps.parentNode(oldElm) ------找到它的父節點
createElm(.....) --------建立新節點
....遞迴地去更新節點
return vnode.elm
}
登入後複製
sameNode
,判斷是否值得我們去給他打修補程式,不值得的話就按照上述步驟進行替換,我們還是去尋找一下這個函數function sameVnode(a, b) {
return (
a.key === b.key && ----------------------key值相等, 這就是為什麼我們推薦要加上key,可以讓判斷更準確
a.asyncFactory === b.asyncFactory &&
((a.tag === b.tag && ---------------------標籤相等
a.isComment === b.isComment && ---------是否為註釋節點
isDef(a.data) === isDef(b.data) && ----比較data是否都不為空
sameInputType(a, b)) || ---------------當標籤為input的時候,需要比較type屬性
(isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error)))
)
}
登入後複製
patchVNode
函數 function patchVnode(...
) {
if (oldVnode === vnode) { //兩個節點一致,啥也不用管,直接返回
return
}
....
if (
//新舊節點都是靜態節點,且key值相等,則明整個元件沒有任何變化,還在之前的範例,賦值一下後直接返回
isTrue(vnode.isStatic) &&
isTrue(oldVnode.isStatic) &&
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.componentInstance = oldVnode.componentInstance
return
}
const oldCh = oldVnode.children //獲取舊節點孩子
const ch = vnode.children //獲取新節點孩子
if (isUndef(vnode.text)) { //新節點沒有文字
if (isDef(oldCh) && isDef(ch)) { //舊節點孩子和新節點孩子都不為空
if (oldCh !== ch) //舊節點孩子不等於新節點孩子
updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) //重點----比較雙方的孩子進行diff演演算法
} else if (isDef(ch)) { //新節點孩子不為空,舊節點孩子為空
....
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) //設定為新節點的文字
}
}
登入後複製
oldStartIdx
,newStartIdx
指向舊節點頭,新節點頭, 初始值為0oldEndIdx
, newEndIdx
指向舊節點尾,新節點尾,初始值為長度-1 let oldStartIdx = 0 //舊頭指標
let newStartIdx = 0 //新頭指標
let oldEndIdx = oldCh.length - 1 //舊尾指標
let newEndIdx = newCh.length - 1 //新尾指標
let oldStartVnode = oldCh[0] //舊頭結點
let oldEndVnode = oldCh[oldEndIdx] //舊尾結點
let newStartVnode = newCh[0] //新頭結點
let newEndVnode = newCh[newEndIdx] //新尾結點
登入後複製
注意: 這裡只要能夠命中一個,就重開,都不能命中的話再看下一環節, 而不是繼續挨個判斷
function updateChildren(){
·....
//好戲從這裡開始看
//只要滿足 舊頭指標<=舊尾指標 同時 新頭指標<= 新尾指標 -- 也可以理解為不能交叉
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
//這裡進行一個矯正,是應該在迴圈的過程中,如果進入key表查詢的話複用後會將舊節點置空(後面會說),所以這裡會對其進行一個處理
if (isUndef(oldStartVnode)) { //舊頭結點為空
oldStartVnode = oldCh[++oldStartIdx] // 往右邊走
} else if (isUndef(oldEndVnode)) { //舊尾結點為空
oldEndVnode = oldCh[--oldEndIdx] //往左邊走
//step1
} else if (sameVnode(oldStartVnode, newStartVnode)) { //比較舊頭和新頭,判斷是否值得打修補程式
patchVnode(...) //打修補程式
oldStartVnode = oldCh[++oldStartIdx] //齊頭並進向右走
newStartVnode = newCh[++newStartIdx] //齊頭並進向右走
//step2
} else if (sameVnode(oldEndVnode, newEndVnode)) { //比較舊尾和新尾, 判斷是否值得打修補程式
patchVnode(...) //打修補程式
oldEndVnode = oldCh[--oldEndIdx] //齊頭並進向左走
newEndVnode = newCh[--newEndIdx] //齊頭並進向左走
//step3
} else if (sameVnode(oldStartVnode, newEndVnode)) { //比較舊頭和新尾,判斷是否值得打修補程式
patchVnode(...) //打修補程式
//補完移動節點
canMove && nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx] //舊頭向右走
newEndVnode = newCh[--newEndIdx] //新尾向左走
//step4
} else if (sameVnode(oldEndVnode, newStartVnode)) { //比較舊尾和新頭,判斷是否值得打修補程式
patchVnode(...) //打修補程式
//補完移動節點
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx] //舊尾向左走
newStartVnode = newCh[++newStartIdx] //新頭向右走
}
登入後複製
實踐來一下,就拿上面隨機來的例子吧
oldEndInx
和newEndInx
齊頭並進向左走(注意這裡是不用去移動節點的哦)(左), 然後重開,在step2再次命中...(右)
- 通過上面這個例子,我們把四種情況都命中了一下(一開始隨便畫的圖沒想到都命中了哈哈哈),也成功通過複用節點將真實結點變為預期結果,這裡便是雙端diff一個核心體現了
- 但是如果四種情況都沒有命中的呢(如圖下)
- 則會走向我們最後一個分支,也就是後面介紹的列表尋找
createKeyToOldIdx
函數function createKeyToOldIdx(children, beginIdx, endIdx) {
let i, key
const map = {} //初始化一個物件
for (i = beginIdx; i <= endIdx; ++i) { //從頭到尾
key = children[i].key //提取每一項的key
if (isDef(key)) map[key] = i //key不為空的時候,存入物件,鍵為key,值為下標
}
return map //返回物件
}
//所以該函數的作用其實就是生成了一個節點的鍵為key,值為下標的一個表
登入後複製
findIdxInOld
函數 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 //判斷是否有值得打修補程式的節點,有則返回
}
}
登入後複製
let oldKeyToIdx, idxInOld, vnodeToMove, refElm;
....
else {
if (isUndef(oldKeyToIdx))
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) //傳入的是舊節點孩子,所以生成了一箇舊節點孩子的key表
//使用三目運運算元--- 這裡也是要使用key的原因,key有效的話可以通過表獲取,無效的話則得進行比遍歷比較
idxInOld = isDef(newStartVnode.key) //判斷新頭結點的key是否不為空--是否有效
? oldKeyToIdx[newStartVnode.key] //不為空的的話就到key表尋找該key值物件的舊節點的下標
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) //遍歷尋找舊節點陣列中是否有和新頭結點值得打修補程式的節點,有的話則賦值其下標給idxInOld(不通過key)
if (isUndef(idxInOld)) { //發現找不到了就直接建立新真實節點
createElm(...)
} else { //找到了
vnodeToMove = oldCh[idxInOld] //找到該下標對應的節點
if (sameVnode(vnodeToMove, newStartVnode)) { //進行一個比較判斷是否值得打修補程式
patchVnode(...) //打修補程式
oldCh[idxInOld] = undefined //置空,下次生成表就不會把它加進去
canMove &&nodeOps.insertBefore( parentElm, vnodeToMove.elm,oldStartVnode.elm ) //移動節點
} else {
//不值得打修補程式,建立節點
createElm(...)
}
}
newStartVnode = newCh[++newStartIdx] //新頭指標向前一步走
}
} //--- while迴圈到這裡
登入後複製
key
表key
有效的話,就拿新頭節點的key
去舊節點的key
表找,找不到就建立新的真實節點, 找得到的話就判斷是否值得打修補程式,值得的話就打修補程式後複用節點,然後將該舊節點孩子值置空,不值得就建立新節點key
無效的話,則去遍歷舊節點陣列挨個進行判斷是否值得打修補程式,後續跟上述一樣也使用一下上面的例子運用一下這個步驟,以下都為key有效的情況
(重新放一下圖,方便看)
newStartVnode
的key
值為B,找到舊節點孩子該節點下標為1,則去判斷是否直接打修補程式,值得的話將該舊節點孩子置空再在A前面插入B右圖的表中B沒有變為undefined是因為表示一開始就生成的,在下次進入迴圈的時候生成的表才會沒有B
undefined
(圖右),直接向右走,重開key
表,發現找不到,於是建立新節點M插入key
表去尋找,找到了D,於是移動插入,舊節點孩子的D置空,同時新頭向前一步走 if (oldStartIdx > oldEndIdx) { //舊的交叉了,說明新增的節點可能還沒加上呢
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(....) //新增
} else if (newStartIdx > newEndIdx) { //新的交叉了,說明舊節點多餘的可能還沒刪掉呢
removeVnodes(oldCh, oldStartIdx, oldEndIdx) //把後面那一段刪掉
}
登入後複製
到這裡updateChildren函數就結束嘍,自己推導一下節點的變化就會很清晰啦
(學習視訊分享:)
以上就是一文搞懂vue2 diff演演算法(附圖)的詳細內容,更多請關注TW511.COM其它相關文章!