中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

vue2的diff算法怎么使用

發布時間:2022-11-11 09:08:24 來源:億速云 閱讀:104 作者:iii 欄目:編程語言

這篇文章主要介紹“vue2的diff算法怎么使用”,在日常操作中,相信很多人在vue2的diff算法怎么使用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”vue2的diff算法怎么使用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

vue2的diff過程

  • 比較方式: 同級比較,不會跨級比較

以下源碼來自vue/patch.ts,會有一些提取,相關函數會附上鏈接。

patch函數
  • diff過程就是調用 patch函數,比較新舊節點,一邊比較一邊給真實DOM打補丁,那么我們就先來看一下patch函數:

  • 源碼地址:  patch函數,isUndef()函數,isDef()函數, emptyNodeAt函數

  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
  }

vue2的diff算法怎么使用

sameNode函數
  • 其中出現了sameNode,判斷是否值得我們去給他打補丁,不值得的話就按照上述步驟進行替換,我們還是去尋找一下這個函數

  • 源碼地址: 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函數

patchVNode
  • 源碼地址: 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) //設置為新節點的文本
    }
  }
  • 這里的判斷很多,所以我也加了個流程圖

vue2的diff算法怎么使用

updateChildren(diff算法的體現)
  • 源碼地址:updateChildren函數

  • 這里采用四步走的形式,我把代碼都拆分出來了

初始化
  • 采用的四個指針分別指向四個節點

    • oldStartIdxnewStartIdx 指向舊節點頭,新節點頭, 初始值為0

    • oldEndIdxnewEndIdx 指向舊節點尾,新節點尾,初始值為長度-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]  //新尾結點

vue2的diff算法怎么使用

四次比較-循環中
  • 舊頭和新頭

  • 舊尾和新尾

  • 舊頭和新尾

  • 舊尾和新頭

注意: 這里只要能夠命中一個,就重開,都不能命中的話再看下一環節, 而不是繼續挨個判斷vue2的diff算法怎么使用

  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] //新頭向右走
      }

實踐來一下,就拿上面隨機來的例子吧

  • step1 、step2

vue2的diff算法怎么使用vue2的diff算法怎么使用

  • step3 、step4(命中)

vue2的diff算法怎么使用vue2的diff算法怎么使用

  • 在step4進行處理,移動節點到正確位置(插在舊頭的前面)vue2的diff算法怎么使用

  • 舊尾向左走,新頭向右走vue2的diff算法怎么使用

  • 處理完后就重開,從step1開始,到step2再次命中,此時oldEndInxnewEndInx齊頭并進向左走(注意這里是不用去移動節點的哦)(左), 然后重開,在step2再次命中...(右)

vue2的diff算法怎么使用vue2的diff算法怎么使用

  • 重開, 這次在step3命中,然后將舊頭結點結點的真實節點插在舊尾結點的后面,到這里其實真實節點就已經是我們所期望的了

vue2的diff算法怎么使用vue2的diff算法怎么使用

  • 上述處理完后,舊頭向右走,新尾向左走,命中step1,新頭和舊頭都向左走,出現交叉情況,至此退出循環

vue2的diff算法怎么使用vue2的diff算法怎么使用

  • 通過上面這個例子,我們把四種情況都命中了一下(一開始隨便畫的圖沒想到都命中了哈哈哈),也成功通過復用節點將真實結點變為預期結果,這里便是雙端diff一個核心體現了

  • 但是如果四種情況都沒有命中的呢(如圖下)vue2的diff算法怎么使用

  • 則會走向我們最后一個分支,也就是后面介紹的列表尋找vue2的diff算法怎么使用

列表尋找-循環中
  • 先來看懂里面涉及到的createKeyToOldIdx函數

  • 源碼地址: 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函數

  • 源碼地址: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有效的情況vue2的diff算法怎么使用

(重新放一下圖,方便看)

  • 生成了一個舊節點的key表(key為鍵,值為下標), 然后newStartVnodekey值為B,找到舊節點孩子該節點下標為1,則去判斷是否直接打補丁,值得的話將該舊節點孩子置空再在A前面插入B

vue2的diff算法怎么使用vue2的diff算法怎么使用

右圖的表中B沒有變為undefined是因為表示一開始就生成的,在下次進入循環的時候生成的表才會沒有B

  • 然后將新頭向右走一步,然后重開,發現前四步依舊沒有命中,此時新頭結點為B,但是生成的舊節點表沒有B,故創建新的節點,然后插入

vue2的diff算法怎么使用vue2的diff算法怎么使用

  • 新頭繼續向右走,重開,命中step1(如圖左), 之后新頭和舊頭齊頭并進向右走, 此時,舊頭指向的undefined(圖右),直接向右走,重開

vue2的diff算法怎么使用vue2的diff算法怎么使用

  • 發現此時又都沒有命中, 此時也是生成一個key表,發現找不到,于是創建新節點M插入

vue2的diff算法怎么使用vue2的diff算法怎么使用

  • 然后新頭繼續向前走,依舊都沒有命中,通過key表去尋找,找到了D,于是移動插入,舊節點孩子的D置空,同時新頭向前一步走

vue2的diff算法怎么使用vue2的diff算法怎么使用

  • 走完這一步其實就出現交叉情況了,退出循環,此時如下圖,你會發現,誒,前面確實得到預期了,可是后面還有一串呢

vue2的diff算法怎么使用

  • 別急,這就來處理

處理
    if (oldStartIdx > oldEndIdx) { //舊的交叉了,說明新增的節點可能還沒加上呢
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(....) //新增
    } else if (newStartIdx > newEndIdx) {  //新的交叉了,說明舊節點多余的可能還沒刪掉呢
      removeVnodes(oldCh, oldStartIdx, oldEndIdx) //把后面那一段刪掉
    }
  • 對于上面這個例子,就是把后面那一段,通過遍歷的方式,挨個刪除vue2的diff算法怎么使用

Vue的優點

Vue具體輕量級框架、簡單易學、雙向數據綁定、組件化、數據和結構的分離、虛擬DOM、運行速度快等優勢,Vue中頁面使用的是局部刷新,不用每次跳轉頁面都要請求所有數據和dom,可以大大提升訪問速度和用戶體驗。

到此,關于“vue2的diff算法怎么使用”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

昌黎县| 白沙| 团风县| 肥城市| 盐亭县| 华宁县| 邵阳市| 响水县| 滁州市| 新乡市| 梅州市| 阿拉善盟| 衡山县| 泉州市| 两当县| 班玛县| 吕梁市| 揭西县| 和平县| 化州市| 新泰市| 铜山县| 穆棱市| 奈曼旗| 读书| 开江县| 乐清市| 七台河市| 阳曲县| 疏勒县| 阜阳市| 聂拉木县| 石泉县| 繁昌县| 马山县| 鄂伦春自治旗| 怀集县| 贵溪市| 富源县| 霍林郭勒市| 宁海县|