日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Vue源码终笔-VNode更新与diff算法初探

發布時間:2023/11/27 生活经验 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Vue源码终笔-VNode更新与diff算法初探 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  寫完這個就差不多了,準備干新項目了。

  確實挺不擅長寫東西,感覺都是羅列代碼寫點注釋的感覺,這篇就簡單闡述一下數據變動時DOM是如何更新的,主要講解下其中的diff算法。

?

  先來個正常的html模板:

    <body><div id='app'><div v-for="item in items">{{item}}</div><div @click='click'>click me!</div></div></body><script src='./vue.js'></script><script>new Vue({el: '#app',data: {items: [1]},methods: {click: function() {this.items.push(2);}}})

  頁面上有一個通過v-for渲染的div,還有一個按鈕,點擊按鈕時會讓div數量+1。

  

  首先需要提到的是,每一次渲染DOM,都會保存一份當前虛擬DOM的副本掛載到_vnode屬性上,如圖:

  點擊前,整個VNode結構為:根節點及3個子節點,子節點均包含2個div標簽和一個空白文本節點,div包含對應的文本節點。

  點擊后,由于vue劫持了部分數組方法,所以會進入自定義的push方法中,將彈入的新元素進行廣播,過程就不看了。

  完成數組添加后,會生成一個新的render函數與新的VNode,diff算法就是比較新舊VNode的差異,通過最小的變化操作渲染新的DOM。

  講VNode的diff算法之前,有一個小點先講一下:如何判斷當前VNode可復用?

  銷毀一個DOM節點并創建一個新的再插入是消耗非常大的,無論是DOM對象本身的復雜性還是操作引起的重繪重排,所以虛擬DOM的目標是盡可能復用現有DOM進行更新。

  其中涉及的概念就是新的VNode能否在舊的基礎上修改并復用呢?有一個函數就是做這個判斷的:

    function sameVnode(a, b) {return (// key來源于v-for或者自定的:key屬性a.key === b.key &&a.tag === b.tag &&a.isComment === b.isComment &&isDef(a.data) === isDef(b.data) &&sameInputType(a, b))}

  該判斷有5重標準:

  (1)key:key屬性如果沒有設置默認是undefined,當且僅當v-for的列表渲染中會給節點加一個唯一的key,形式如圖:,key不一樣的節點不進行復用,官方文檔也有說明設置key屬性可以強制重新生成一個新DOM。

  (2)tag:復用的節點必須保證標簽名一致,畢竟沒有更改tag名的API

  (3)isComment:注釋與普通的DOM不是一個次元,所以需要判斷

  (4)isDef(*.data):這個涉及屬性的更新,如果一個節點沒有任何屬性,即data為undefined,與一個有data屬性的節點進行更新不如直接渲染一個新的

  (5)sameInputType:這個主要是input標簽type屬性異同判斷,不同的type相當于不同的tag

  如果均滿足,可以判定該節點可復用。

?

  前面說了,每一個更改數據源,會生成一個新的VNode,來與舊的VNode進行比較,節點間的比較無非是判斷是否可復用,再進行屬性置換。

  而diff算法主要是針對子節點的更新,即兩個數組之間的異同比較與更新。

  一個數組的變化無非3個狀態:增、刪、改,但是其中增刪會涉及數組索引與對應元素的變動,總體來講還是比較復雜的。

  源碼中有一個函數專門處理子節點比較,整體如下:

    function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {// var... 
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {// 舊VNode不存在if (isUndef(oldStartVnode)) {// ...} else if (isUndef(oldEndVnode)) {// ...} else if (sameVnode(oldStartVnode, newStartVnode)) {// ...} else if (sameVnode(oldEndVnode, newEndVnode)) {// ...} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right// ...} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left// ...} else {// ... }}if (oldStartIdx > oldEndIdx) {// ...} else if (newStartIdx > newEndIdx) {// ... }}

  第一次看還是比較懵逼的,主路線while循環中有7重判斷,分別對應7種情況。

  分解本例中的情況,不貼代碼,嘗試畫個圖:

  

  對比新舊VNode,可以看出新的VNode在索引0的后面插入了一個新的tag

  接下來通過updateChildren函數進行比較,有很多的變量,這里還需要一個圖:

  在函數中有8個變量,其中4個舊VNode,4個新VNode,分別是一一對應的,解釋一半就行了:

    var oldStartIdx = 0;var newStartIdx = 0;var oldEndIdx = oldCh.length - 1;var oldStartVnode = oldCh[0];var oldEndVnode = oldCh[oldEndIdx];var newEndIdx = newCh.length - 1;var newStartVnode = newCh[0];var newEndVnode = newCh[newEndIdx];

  (1)oldStartIdx => 從前往后的舊VNode數組索引,初始化時為0 => 簡稱為前索引

  (2)oldStartVnode => 對應索引的舊VNode元素 => 簡稱為前元素

  (3)oldEndIdx => 從后往前的舊VNode數組索引,初始化為children的數組長度 => 簡稱為后索引

  (4)oldEndVnode => 對應索引的舊Vnode元素 => 簡稱為后元素

  后面的闡述全部用簡稱,不然太難講了,并且新VNode的數組簡稱newCh,舊VNode的數組簡稱oldCh

  另外4個變量只是將old更換為new,并對應新VNode的索引與元素。

  

  接下來是一個大while循環,終止條件是前索引大于后索引(newCh或oldCh):

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (isUndef(oldStartVnode)) {// ...} else if (isUndef(oldEndVnode)) {// ...} else if (sameVnode(oldStartVnode, newStartVnode)) {// ...} else if (sameVnode(oldEndVnode, newEndVnode)) {// ...} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right// ...} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left// ...} else {// ...
        }}

?  由于有幾種情況我模擬不出來,只能大概過一下。

1、isUndef(oldStartVnode)、isUndef(oldEndVnode)

  前兩種是oldCh前元素oldCh后元素不存在,我能模擬的情況是當oldCh中沒有元素時,會出現這種情況。

  這時只是單純加前索引加1或者后索引減1,而oldCh長度此時為0,會立即跳出while循環,進入下一步。

2、sameVnode(a,b)

  下面的的4種情況都是判斷節點是否可復用,然后進行更新。其中對比的情況有4對:

  oldCh前元素 => newCh前元素

  oldCh后元素 => newCh后元素

  oldCh前元素 => newCh后元素

  oldCh后元素 => newCh前元素?

  取第一種情況來說,如果比較通過,說明oldCh前元素可以被復用,隨即調用patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)來對DOM進行更新,由于tag是不變的,可以直接對DOM進行各種API調用,比如說事件更改,只要remove舊事件,add新事件就行,這里只是DOM對象的屬性更改,不會影響到DOM的增刪。

  當patch完畢后,會將oldCh前索引newCh的前索引加1,并更新對應的元素,然后進入下一輪循環。

  畫一輪圖解釋:

  

  此時第一個子節點已經更新完畢,然后重新開始對比,如果oldCh與newCh的索引1處也可復用,會再次更新并加1,直到前索引大于后索引時,說明所有可能的比較都進行完畢。

  這里的4種比較沒有必要重復過一遍,如果是前索引就加1,后索引就減1。

3、else{...}

  最后一種情況是需要強制更新元素時才會有的情況,比如:

    <body><div id='app'><div v-if="!vIfIter" key='o'>old Ele1</div><div v-if="vIfIter" key='n'>new Ele</div><div @click='click'>click me!</div></div></body><script src='./vue.js'></script><script>new Vue({el: '#app',data: {vIfIter: false},methods: {click: function() {this.vIfIter = true;}}})</script>

  此時,由于設置了單獨的key值,所以div被標記為不可復用,跳過了所有判斷進入了else階段:

    // 這里將舊VNode中剩余的元素key值作為對象輸出if (isUndef(oldKeyToIdx)) {oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);}// 判斷新VNode中是否存在可復用的元素idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null;// 不存在就創建一個新的插入DOM中if (isUndef(idxInOld)) {// New element
    }// 存在 else {elmToMove = oldCh[idxInOld];if (sameVnode(elmToMove, newStartVnode)) {// 更新VNode
            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);// 把舊的VNode置空 此處會觸發到while循環的前兩個判斷oldCh[idxInOld] = undefined;// 移動更新后的VNodecanMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm);newStartVnode = newCh[++newStartIdx];}// 同樣的key值不同的tag 創建新DOM插入else {// same key but different element. treat as new element
        }}

  簡單來講還是可復用就復用,不可復用創建新DOM插入。

?

  最后來看看while循環跳出來的語句,其實很簡單:

    // VNode數量增加了if (oldStartIdx > oldEndIdx) {// 如果VNode是中間插入就會存在refElm// 否則refElm為null 調用insertBefore會將DOM插入父元素尾部refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);}// 減少了 else if (newStartIdx > newEndIdx) {// 移除多出來的DOM節點
        removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);}

?

  至此,所有的分析完了,上面的案例有興趣可以自己跑跑。

  

  不容易啊,寫完了。。。已經入行5個月,由于沒有什么好項目練手,只能看源碼提升基本功,接下來可能很長時間不寫博客了。(反正也沒人看,啊哈哈哈哈~)

  (定個小目標,Codewars刷到3kyu,加油!)

轉載于:https://www.cnblogs.com/QH-Jimmy/p/7449789.html

總結

以上是生活随笔為你收集整理的Vue源码终笔-VNode更新与diff算法初探的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。