<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          請闡述vue的diff算法

          共 6721字,需瀏覽 14分鐘

           ·

          2021-05-08 20:04










          前端獵手
           鏈接每一位開發(fā)者,讓編程更有趣兒!
          關注

          diff是什么?diff就是比較兩棵樹,render會生成兩顆樹,一棵新樹newVnode,一棵舊樹oldVnode,然后兩棵樹進行對比更新找差異就是diff,全稱difference,在vue里面 diff 算法是通過patch函數(shù)來完成的,所以有的時候也叫patch算法

          ? diff 發(fā)生的時機

          diff發(fā)生在什么時候呢?當然我們可以說在數(shù)據(jù)更新的時候發(fā)生diff,因為數(shù)據(jù)更新會運行render函數(shù)得到虛擬dom樹,最后頁面重新渲染。

          當組件創(chuàng)建的時候,組件所依賴的屬性或者數(shù)據(jù)變化時,會運行一個函數(shù) (下面代碼中的updateComponent),該函數(shù)會做兩件事:

          • 運行_render生成一顆新的虛擬dom樹(vnode tree)
          • 運行_updata,傳入_render生成的虛擬dom樹的根節(jié)點,對新舊兩棵樹進行對比,最終完成對真實dom的更新

          核心代碼如下,跟原代碼有所差異,但都差不多,是這么個意思:

          // vue構造函數(shù)
          function Vue(){
            // ... 其他代碼
            var updateComponent = () => {
              this._update(this._render());
            }
            new Watcher(updateComponent);
            // ... 其他代碼
          }

          diff就發(fā)生在_update函數(shù)的運行過程中

          代碼中先調(diào)用_render函數(shù)得到虛擬dom根節(jié)點,然后傳入_update函數(shù)中,在將updateComponent傳入Watcher中,watcher可以監(jiān)聽函數(shù)執(zhí)行的過程,監(jiān)測函數(shù)執(zhí)行期間用到了哪些響應式數(shù)據(jù)并且進行依賴收集,關于watcher可以瞅瞅我上一篇文章:一文帶你了解vue2之響應式原理

          ?? _update函數(shù)在干什么?

          _update函數(shù)會接收到一個vonde參數(shù),這就是生成的虛擬dom樹,同時,_update函數(shù)通過當前組件的_vnode屬性,拿到的虛擬dom樹。_update函數(shù)首先會給組件的_vnode屬性重新賦值,讓它指向新樹

          簡單用代碼表示:

          function update(vnode){
              //vnode新樹
              //this._vnode舊樹
              this._vnode = vnode
          }

          如果只考慮更新虛擬dom樹,這一步已經(jīng)完成了,但是最終目的是要更新頁面,所以就要用到diff進行樹的節(jié)點對比,所以可以保存下舊樹oldVnode用于對比

          簡單用代碼表示:

           <body>
              <div id="app"></div>
              <script src="./vue.js"></script>
              <script>
                const vm = new Vue({
                  el"#app",
                });


                function update(vnode{
                  //vnode新樹
                  //this._vnode舊樹
                  
                  let oldVnode = vm._vnode; //保存舊樹
                  
                  this._vnode = vnode; //更新新樹
                }
              
          </script>

            </body>

          對比oldVnodevnode就行了,「對比的目的就是更新真實dom」

          接下來,會判斷舊樹oldVnode是否存在:

          • 不存在:說明這是第一次加載組件,于是通過內(nèi)部的patch函數(shù),直接遍歷新樹,為每個節(jié)點生成真實DOM,然后掛載到每個節(jié)點的elm屬性上
          image.png

          簡單用代碼表示:

          <body>
              <div id="app"></div>
              <script src="./vue.js"></script>
              <script>
                const vm = new Vue({
                  el"#app",
                });
                console.log(vm);

                function update(vnode{
                  //vnode新樹
                  //this._vnode舊樹
                  let oldVnode = vm._vnode; //保存舊樹
                  this._vnode = vnode; //更新新樹

                  //如果舊樹oldVnode不存在
                  if(!oldVnode){
                      this.__patch__(vm.$el,vnode);
                  }
                }
              
          </script>

            </body>
          • 存在:說明之前已經(jīng)渲染過該組件,于是通過內(nèi)部的patch函數(shù),對新舊兩棵樹進行對比,從而達到下面兩個目標:
          1. 完成對所有真實dom的最小化處理
          2. 讓新樹的節(jié)點對應合適的真實dom

          ?? patch函數(shù)的對比流程

          術語解釋:一會看到以下字眼,均代表以下意思

          1.「相同」:是指兩個虛擬節(jié)點的標簽類型和key值均相同,但input元素還要看type屬性。這個術語在vue源碼中叫sameVnode,它是一個函數(shù),用來判斷兩個虛擬節(jié)點是不是同一個節(jié)點

          例:兩個虛擬節(jié)點div是否相同

          <div>法醫(yī)</div>
          <div>前端獵手</
          div>

          標簽類型都為div,key值不僅僅在v-for遍歷中,也可以用在任何標簽中,上面兩個div中沒有key值,所以都為undefined,所以標簽類型和key值都相同,不用看內(nèi)容是否相同,它是另一個節(jié)點:文本節(jié)點

          <div key="fayi">法醫(yī)</div>
          <div key="qdls">前端獵手</
          div>

          上面兩個虛擬節(jié)點是不同的,因為key值不同

           <input type="text">
           <input type="radio">

          上面兩個虛擬節(jié)點是不同的,因為input不僅僅要看key值和標簽類型,還要看type是否相同

          2.「新建元素」:是指根據(jù)一個虛擬節(jié)點提供的信息,創(chuàng)建一個真實dom元素,同時掛載到虛擬節(jié)點的elm屬性上

          3.「銷毀元素」:是指:vnode.elm.remove()

          4.「更新」:是指對兩個虛擬節(jié)點進行對比更新,它僅發(fā)生在兩個虛擬節(jié)點「相同」的情況下。具體過程稍后描述。

          5.「對比子節(jié)點」:是指對兩個虛擬節(jié)點的子節(jié)點進行對比,具體過程稍后描述

          詳細流程

          根節(jié)點比較

          patch函數(shù)首先對根節(jié)點進行對比

          如果兩個節(jié)點:

          • 「相同」,進入 「更新」 流程
          1. 將舊節(jié)點的真實dom賦值到新節(jié)點:newVnode.elm = oldVnode.elem,舊節(jié)點會被垃圾回收機制回收
          2. 對比新節(jié)點和舊節(jié)點的屬性,有變化的更新到真實dom中
          3. 當前新舊兩個節(jié)點處理完成,開始 「對比子節(jié)點」
          • 「相同」
          1. 新節(jié)點「遞歸」「新建元素」
          2. 舊節(jié)點 「銷毀元素」

          「對比子節(jié)點」

          虛擬dom樹已經(jīng)完成,就剩修改真實dom了,但是修改真實dom的效率是比較耗時的,vue的原則是能不改就不改,盡量啥也別做,在「對比子節(jié)點」時,vue一切的出發(fā)點,都是為了:

          • 盡量啥也別做

          • 不行的話,盡量僅改動元素屬性

          • 還不行的話,盡量移動元素,而不是刪除和創(chuàng)建元素

          • 實在不行的話,刪除和創(chuàng)建元素

          「對比流程:」

          圖片說明:

          • 黃色圓圈:表示舊子節(jié)點和新子節(jié)點所對應的相同節(jié)點類型
          • 數(shù)字:表示key值,用來區(qū)分是不是同一個節(jié)點
          • 藍色方塊:表示對比之前舊子節(jié)點所對應的真實dom
          • 箭頭:分別表示頭指針和尾指針

          接下來,我們要做的就是對比舊子節(jié)點新子節(jié)點之間的差異,目標是改變真實dom,并且將新虛擬子節(jié)點對應到真實dom里面去,vue使用兩個指針分別指向新舊子節(jié)點樹的頭和尾

          步驟:

          1. 首先對比新樹和舊樹的頭指針,瞅瞅兩個節(jié)點是否一樣,從圖中可以看到是一樣的,如果一樣則進入 「更新」 流程:先將舊節(jié)點的真實dom賦值到新節(jié)點(真實dom連線到新子節(jié)點),然后循環(huán)對比新舊節(jié)點的屬性,看看有沒有不一樣的地方,將有變化的更新到真實dom中,最后還要采用深度優(yōu)先(一顆樹的節(jié)點走到盡頭,再走另一個節(jié)點)的方式遞歸循環(huán)這兩個新舊子節(jié)點是否還有子節(jié)點,如果存在,則同理,這里我們就假設它不存子節(jié)點。灰色表示已經(jīng)處理完成,然后兩個頭指針往后移動
          1. 接下來,繼續(xù)比較兩個頭指針,看看兩個節(jié)點是否一樣,很明顯,兩個節(jié)點是不一樣的,因為key值不同,不一樣的時候它不會銷毀刪除從建立,吃個??壓壓驚,淡定!前面有提到盡量別操作dom,它一定會找到一樣的節(jié)點,一條道走到黑,然后會對比尾指針,可以看到尾指針是一樣的,跟第一步是一樣的:一頓操作猛如虎,先將舊節(jié)點的真實dom賦值到新節(jié)點(真實dom連線到新子節(jié)點),然后循環(huán)對比新舊節(jié)點的屬性,將有變化的更新到真實dom中,接著還要遞歸循環(huán)這兩個新舊子節(jié)點是否還有子節(jié)點,最后兩個尾指針往前移動
          1. 然后繼續(xù)比較頭指針,很明顯不一樣,尾指針呢?也不一樣,因為key值還是不一樣。隨后它會比較頭指針和尾指針,看看是否一樣,可以看到舊節(jié)點的圓2頭指針和新節(jié)點圓2尾指針是一樣的,所以操作跟前兩步是一樣的,又是一頓操作猛如虎,結果如下圖:

          這里我們要注意的是真實dom必須和新虛擬子節(jié)點要一一對應上的,所以除了更新變化的地方之外還要進行位置移動,移動到舊樹尾指針的后面,最后舊樹頭指針往后移動,新樹尾指針往前移動,如下圖:

          1. 繼續(xù)比對,新舊頭指針不同,尾指針不同,兩個頭尾也不同,然后它會以新樹頭指針為基準,循環(huán)舊虛擬子節(jié)點,看看新樹圓3是否存在于舊虛擬子節(jié)點,存在的話在哪個位置,找到之后進行復用,連線,有變化的地方更新到真實dom,操作跟前面幾步一樣,真實dom也要進行位置移動,移動到舊樹頭指針之前。隨后新樹頭指針繼續(xù)往后移動到圓9位置,如下圖:
          1. 繼續(xù)比對,新舊頭指針不同,尾指針不同,但新樹頭指針和舊樹尾指針相同,操作跟前面幾步相同,但依然需要進行位置移動,移動到舊樹頭指針之前。隨后新樹頭指針往后移動,與新樹尾指針重合,舊樹尾指針向前移動到圓1位置,如下圖:
          1. 繼續(xù)比對,新舊兩樹頭指針不同,尾指針不同,兩個頭尾也不同,然后它以新樹頭指針為基準,循環(huán)舊虛擬子節(jié)點,找圓8在舊樹中存不存在,從圖中可以看出,并不存在,這個時候確實沒辦法了,只能 「新建元素」。隨后新樹頭指針繼續(xù)向后移動到圓2位置,如圖:
          1. 當頭指針移動到圓2位置時,頭指針已經(jīng)不再是有效的了,當頭指針超過尾指針的時候,循環(huán)結束,從過程我們可以看到新樹先循環(huán)完成,但是舊樹還有剩余的節(jié)點,這說明舊樹中剩余的節(jié)點都是應該被刪除的節(jié)點,所對應的真實dom也會被移除

          最終真實dom生成完畢,整個過程我們只新建了一個元素,如下圖:

          在面試的時候也會被問到關于diff算法的問題,以下是參考回答:

          當組件創(chuàng)建和更新時,vue會執(zhí)行內(nèi)部的update函數(shù),該函數(shù)使用render函數(shù)生成的虛擬dom樹,將新舊兩樹進行對比,找到差異點,最終更新到真實dom

          對比差異的過程叫diff,vue在內(nèi)部通過一個叫patch的函數(shù)完成該過程

          在對比時,vue采用深度優(yōu)先、同級比較的方式進行比對。同級比較就是說它不會跨越結構進行比較

          在判斷兩個節(jié)點是否相同時,vue是通過虛擬節(jié)點的key和tag來進行判斷的

          具體來說,首先對根節(jié)點進行對比,如果相同則將舊節(jié)點關聯(lián)的真實dom的引用掛到新節(jié)點上,然后根據(jù)需要更新屬性到真實dom,然后再對比其子節(jié)點數(shù)組;如果不相同,則按照新節(jié)點的信息遞歸創(chuàng)建所有真實dom,同時掛到對應虛擬節(jié)點上,然后移除掉舊的dom。

          在對比其子節(jié)點數(shù)組時,vue對每個子節(jié)點數(shù)組使用了兩個指針,分別指向頭尾,然后不斷向中間靠攏來進行對比,這樣做的目的是盡量復用真實dom,盡量少的銷毀和創(chuàng)建真實dom。如果發(fā)現(xiàn)相同,則進入和根節(jié)點一樣的對比流程,如果發(fā)現(xiàn)不同,則移動真實dom到合適的位置。

          這樣一直遞歸的遍歷下去,直到整棵樹完成對比。

          ?? 好了, 以上就是我的分享,大家對于diff算法還有其它理解的話可以在評論區(qū)討論鴨~

          希望小伙伴們點贊 ?? 支持一下哦~ ??,我會更有動力的 ??



          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本高清视频一区二区三区 | 在线免费观看黄 | 三级久久网 | 青草影视视频 | 无码视频在线免费观看 |