首頁 web前端 js教程 vue的diff演算法知識點總結

vue的diff演算法知識點總結

May 28, 2018 am 11:26 AM
diff 總結 知識點

本篇文章給大家分享了關於vue的diff演算法的相關知識點總結,有興趣的朋友參考學習下。

虛擬dom

diff演算法首先要明確一個概念就是diff的物件是虛擬dom,更新真​​實dom則是diff演算法的結果

Vnode基底類別

 constructor (
  。。。
 ) {
  this.tag = tag
  this.data = data
  this.children = children
  this.text = text
  this.elm = elm
  this.ns = undefined
  this.context = context
  this.fnContext = undefined
  this.fnOptions = undefined
  this.fnScopeId = undefined
  this.key = data && data.key
  this.componentOptions = componentOptions
  this.componentInstance = undefined
  this.parent = undefined
  this.raw = false
  this.isStatic = false
  this.isRootInsert = true
  this.isComment = false
  this.isCloned = false
  this.isOnce = false
  this.asyncFactory = asyncFactory
  this.asyncMeta = undefined
  this.isAsyncPlaceholder = false
 }
登入後複製

這個部分的程式碼主要是為了更好地知道在diff演算法中具體diff的屬性的意義,當然也可以更好地了解vnode實例

整體過程

核心函數是patch函數

  • #isUndef判斷(是不是undefined或null)

  • #// empty mount (likely as component), create new root elementcreateElm(vnode, insertedVnodeQueue) 這裡可以發現創建節點不是一個一個插入,而是放入一個隊列中統一批次

  • #核心函數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)
   )
  )
 )
}
登入後複製

這裡是一個外層的比較函數,直接去比較了兩個節點的key,tag(標籤),data的比較(注意這裡的data指的是VNodeData),input的話直接比較type。

export interface VNodeData {
 key?: string | number;
 slot?: string;
 scopedSlots?: { [key: string]: ScopedSlot };
 ref?: string;
 tag?: string;
 staticClass?: string;
 class?: any;
 staticStyle?: { [key: string]: any };
 style?: object[] | object;
 props?: { [key: string]: any };
 attrs?: { [key: string]: any };
 domProps?: { [key: string]: any };
 hook?: { [key: string]: Function };
 on?: { [key: string]: Function | Function[] };
 nativeOn?: { [key: string]: Function | Function[] };
 transition?: object;
 show?: boolean;
 inlineTemplate?: {
  render: Function;
  staticRenderFns: Function[];
 };
 directives?: VNodeDirective[];
 keepAlive?: boolean;
}
登入後複製

這會確認兩個節點是否有進一步比較的價值,不然直接替換

替換的過程主要是一個createElm函數另外則是銷毀oldVNode

// destroy old node
    if (isDef(parentElm)) {
     removeVnodes(parentElm, [oldVnode], 0, 0)
    } else if (isDef(oldVnode.tag)) {
     invokeDestroyHook(oldVnode)
    }
登入後複製

插入過程簡化來說就是判斷node的type分別呼叫

createComponent(會判斷是否有children然後遞迴呼叫)

createComment

createTextNode

#建立後使用insert函數

之後需要用hydrate函數將虛擬dom和真是dom進行映射

function insert (parent, elm, ref) {
  if (isDef(parent)) {
   if (isDef(ref)) {
    if (ref.parentNode === parent) {
     nodeOps.insertBefore(parent, elm, ref)
    }
   } else {
    nodeOps.appendChild(parent, elm)
   }
  }
 }
登入後複製

核心函數

 function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
  if (oldVnode === vnode) {
   return
  }

  const elm = vnode.elm = oldVnode.elm

  if (isTrue(oldVnode.isAsyncPlaceholder)) {
   if (isDef(vnode.asyncFactory.resolved)) {
    hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
   } else {
    vnode.isAsyncPlaceholder = true
   }
   return
  }

  if (isTrue(vnode.isStatic) &&
   isTrue(oldVnode.isStatic) &&
   vnode.key === oldVnode.key &&
   (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  ) {
   vnode.componentInstance = oldVnode.componentInstance
   return
  }

  let i
  const data = vnode.data
  if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
   i(oldVnode, vnode)
  }

  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 (isDef(oldVnode.text)) nodeOps.setTextContent(elm, &#39;&#39;)
    addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
   } else if (isDef(oldCh)) {
    removeVnodes(elm, oldCh, 0, oldCh.length - 1)
   } else if (isDef(oldVnode.text)) {
    nodeOps.setTextContent(elm, &#39;&#39;)
   }
  } 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)
  }
 }
登入後複製

const el = vnode.el = oldVnode.el 這是很重要的一步,讓vnode.el引用到現在的真實dom,當el修改時,vnode.el會同步變化。

  1. 比較二者引用是否一致

  2. 之後asyncFactory不知道是做什麼的,所以這個比較看不懂

  3. 靜態節點比較key,相同後也不做重新渲染,直接拷貝componentInstance(once指令在此生效)

  4. 如果vnode是文字節點或註解節點,但是vnode.text != oldVnode.text時,只需要更新vnode.elm的文字內容就可以

  5. children的比較

  • #如果只有oldVnode有子節點,那就把這些節點都刪除

  • 如果只有vnode有子節點,那就建立這些子節點,這裡如果oldVnode是個文字節點就把vnode.elm的文字設定為空字串

  • 都有則updateChildren,這個之後詳述

  • 如果oldVnode和vnode都沒有子節點,但oldVnode是文字節點或註解節點,就把vnode.elm的文字設定為空字串

updateChildren

#這部分重點還是關注整個演算法

首先四個指針,oldStart,oldEnd,newStart,newEnd,兩個數組,oldVnode,Vnode。

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

  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)
    oldStartVnode = oldCh[++oldStartIdx]
    newStartVnode = newCh[++newStartIdx]
   } else if (sameVnode(oldEndVnode, newEndVnode)) {
    patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
    oldEndVnode = oldCh[--oldEndIdx]
    newEndVnode = newCh[--newEndIdx]
   } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
    patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
    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)
    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)
      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(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
 }
登入後複製

一個循環比較的幾個情況和處理(以下的--均指index的--)比較則是比較的node節點,簡略寫法不嚴謹比較用的是sameVnode函數也不是真的全等

整體循環不結束的條件oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx

  1. oldStart === newStart,oldStart newStart

  2. #oldEnd === newEnd,oldEnd-- newEnd--

  3. #oldStart === newEnd, oldStart插在隊伍結尾oldStart newEnd--

  4. #oldEnd === newStart, oldEnd插到隊伍開頭oldEnd-- newStart

  5. 剩下的所有情況都走這個處理簡單的說也就兩種處理,處理後newStart

  • newStart在old中發現一樣的那麼將這個移到oldStart前

  • 沒有發現一樣的那麼創建一個放到oldStart之前

循環結束後並沒有完成

還有一段判斷才算完

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(parentElm, oldCh, oldStartIdx, oldEndIdx)
  }
登入後複製

簡單的說就是循環結束後,看四個指針中間的內容,old數組中和new數組中,多退少補而已

上面是我整理給大家的,希望今後會對大家有幫助。

相關文章:

Angular5為元件本身的標籤新增樣式class的方法

vue-cli開發環境實作跨網域請求的方法

詳解Vue-cli webpack行動端自動化建置rem問題

以上是vue的diff演算法知識點總結的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1422
52
Laravel 教程
1316
25
PHP教程
1267
29
C# 教程
1239
24
Python速查表大全,你掌握了哪些知識點? Python速查表大全,你掌握了哪些知識點? Apr 26, 2023 pm 10:49 PM

Python作為當下最大眾化的程式語言,相信每天都會有大量的新手朋友進入學習大軍的行列。但無論語言是多麼的容易學習,其基本概念、基礎知識還是比較多的,對於小白來說,一時間要掌握這麼多還是有些吃力。今天精選收集了眾多Python相關的知識速查表,可以說是包羅萬象,以後媽媽再也不用擔心大家記不住任何知識點了! Python基礎Pythonbasics這個速查表包含了所有的Python基本知識,從變數資料型別到列表字串,從環境安裝到常用函式庫的使用,可以說面面俱到。 Beginner'sPytho

總結Linux系統中system()函數的用法 總結Linux系統中system()函數的用法 Feb 23, 2024 pm 06:45 PM

Linux下system()函數的總結在Linux系統中,system()函數是一個非常常用的函數,它可以用來執行命令列指令。本文將對system()函數進行詳細的介紹,並提供一些特定的程式碼範例。一、system()函數的基本用法system()函數的聲明如下:intsystem(constchar*command);其中,command參數是一個字符

揭秘HTML快取機制:不可或缺的知識要點 揭秘HTML快取機制:不可或缺的知識要點 Jan 23, 2024 am 08:51 AM

HTML快取機制大揭密:必備的知識點,需要具體程式碼範例在Web開發中,效能一直是重要的考量。而HTML快取機制是提升Web頁面效能的關鍵之一。本文將揭秘HTML快取機制的原理與實務技巧,並提供具體的程式碼範例。一、HTML快取機制的原理Web頁面存取過程中,瀏覽器透過HTTP協定請求伺服器取得HTML頁面。 HTML快取機制就是將HTML頁面快取在瀏覽器

用vimdiff取代svn diff:比較程式碼的工具 用vimdiff取代svn diff:比較程式碼的工具 Jan 09, 2024 pm 07:54 PM

在linux下,直接使用svndiff指令查看程式碼的修改是很吃力的,於是在網路上搜尋了一個比較好的解決方案,就是讓vimdiff作為svndiff的檢視程式碼工具,尤其對於習慣用vim的人來說真的是很方便。當使用svndiff指令比較某個檔案的修改前後時,例如執行下列指令:$svndiff-r4420ngx_http_limit_req_module.c那麼實際上會向預設的diff程式傳送如下指令:-u-Lngx_http_limit_req_module.c(revision4420)-Lngx_

網路安全入門:初學者必備知識點是什麼? 網路安全入門:初學者必備知識點是什麼? Jun 11, 2023 am 09:57 AM

網路安全入門:初學者必備知識點是什麼?近年來,隨著網路的高速發展,網路安全越來越引起人們的重視。然而,對許多人來說,網路安全依然是一片未知的海洋。那麼,網路安全入門,初學者需要掌握哪些必備知識點呢?本文將為大家整理一下。一、網路攻擊與威脅首先,了解網路攻擊和威脅的類型是入門網路安全所必須掌握的知識點。網路攻擊的種類多種多樣,如釣魚攻擊、惡意軟體、勒索軟體

MySQL資料類型詳解:你需要知道的知識點 MySQL資料類型詳解:你需要知道的知識點 Jun 15, 2023 am 08:56 AM

MySQL是世界上最受歡迎的關聯式資料庫管理系統之一,因其可靠性、高安全性、高擴展性以及相對低的成本而得到了廣泛應用。 MySQL的資料類型定義了各種資料類型的儲存方式,是MySQL的重要組成部分。本文將詳解MySQL的資料類型,以及在實際應用中需要注意的一些知識點。一、MySQL的資料型態分類MySQL的資料型態可以分為以下幾類:整數型態:包括TINYINT、

Oracle資料型態大揭密:你必須了解的知識點 Oracle資料型態大揭密:你必須了解的知識點 Mar 07, 2024 pm 05:18 PM

Oracle資料型態大揭密:你必須了解的知識點,需要具體程式碼範例Oracle作為世界領先的資料庫管理系統之一,在資料儲存與處理中扮演著重要的角色。在Oracle中,資料類型是非常重要的概念,它定義了資料在資料庫中的儲存格式、範圍和操作方式。本文將揭示Oracle資料類型的各種知識點,並透過具體的程式碼範例展示它們的用法和特點。一、常見的資料類型字元型資料類型

探索自然本源!谷歌2022年終總結第七彈:「生化環材」如何吃機器學習紅利? 探索自然本源!谷歌2022年終總結第七彈:「生化環材」如何吃機器學習紅利? Apr 11, 2023 pm 07:19 PM

隨著機器學習和量子計算的巨大進步,我們現在有了更強大的新工具,能夠以新的方式與各行業研究者合作,並從根本上加速突破性科學發現的進展。本期Google年終總結的主題是「自然科學」,文章作者為Google研究院的傑出科學家John Platt,1989年博士畢業於加州理工大學。自從八年前加入Google Research 以來,我有幸成為一個天才研究人員的社群的一員,致力於應用前沿運算技術來推動應用科學的可能性,目前團隊正在探索物理和自然科學的課題,從幫助組織全世界的蛋白質和基因組資訊以造福人們的生活,到利用量

See all articles