首頁 web前端 js教程 diff演算法使用技巧總結

diff演算法使用技巧總結

May 11, 2018 pm 01:35 PM
diff 使用技巧 總結

這次帶給大家diff演算法使用技巧總結,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. #之後asyncFactory不知道是做什麼的,所以這個比較看不懂
  2. 靜態節點比較key,相同後也不做重新渲染,直接拷貝componentInstance(once指令在此生效)
  3. 如果vnode是文字節點或
  4. 註解

    節點,但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

oldStart === newStart,oldStart newStart
  1. #oldEnd === newEnd,oldEnd-- newEnd--
  2. #oldStart === newEnd, oldStart插在隊伍結尾oldStart newEnd--
  3. #oldEnd === newStart, oldEnd插到隊伍開頭oldEnd-- newStart
  4. 剩下的所有情況都走這個處理簡單的說也就兩種處理,處理後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)
  }

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

axios发送post请求提交图片表单步骤详解

axios+post方法提交formdata步骤详解

以上是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

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

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++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 教程
1423
52
Laravel 教程
1318
25
PHP教程
1269
29
C# 教程
1248
24
總結Linux系統中system()函數的用法 總結Linux系統中system()函數的用法 Feb 23, 2024 pm 06:45 PM

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

如何使用 Go 語言進行量化金融分析? 如何使用 Go 語言進行量化金融分析? Jun 11, 2023 am 08:51 AM

在現代金融領域中,隨著數據科學和人工智慧技術的興起,量化金融逐漸成為了越來越重要的方向。而作為一門能夠高效處理資料和部署分散式系統的靜態類型程式語言,Go語言也逐漸受到了量化金融領域的關注。本文將介紹如何使用Go語言進行量化金融分析,具體內容如下:取得金融數據首先,我們需要取得金融數據。 Go語言的網路程式設計能力非常強大,可以用來取得各種金融數據。比

如何使用C#編寫最小生成樹演算法 如何使用C#編寫最小生成樹演算法 Sep 19, 2023 pm 01:55 PM

如何使用C#編寫最小生成樹演算法最小生成樹演算法是一種重要的圖論演算法,它用於解決圖的連結性問題。在電腦科學中,最小生成樹是指一個連通圖的生成樹,該生成樹的所有邊的權值總和最小。本文將介紹如何使用C#編寫最小生成樹演算法,並提供具體的程式碼範例。首先,我們需要定義一個圖的資料結構來表示問題。在C#中,可以使用鄰接矩陣來表示圖。鄰接矩陣是一個二維數組,其中每個元素表示

如何使用 Go 語言進行資料探勘? 如何使用 Go 語言進行資料探勘? Jun 10, 2023 am 08:39 AM

隨著大數據和資料探勘的興起,越來越多的程式語言開始支援資料探勘的功能。 Go語言作為一種快速、安全、高效的程式語言,也可以用於資料探勘。那麼,如何使用Go語言進行資料探勘呢?以下是一些重要的步驟和技術。數據獲取首先,你需要取得數據。這可以透過各種途徑實現,例如爬取網頁上的資訊、使用API​​取得資料、從資料庫讀取資料等等。 Go語言自備了豐富的HTTP

如何使用PHP開發簡單的SEO最佳化功能 如何使用PHP開發簡單的SEO最佳化功能 Sep 20, 2023 pm 04:18 PM

如何使用PHP開發簡單的SEO優化功能SEO(SearchEngineOptimization)即搜尋引擎優化,是指透過改進網站的結構和內容來提高網站在搜尋引擎中的排名,從而獲得更多的自然流量。在網站開發中,如何使用PHP來實現簡單的SEO優化功能呢?本文將介紹一些常用的SEO最佳化技巧和具體的程式碼範例,幫助開發者在PHP專案中實現SEO最佳化。一、使用友好

如何使用nginx進行防盜鏈 如何使用nginx進行防盜鏈 Jun 11, 2023 pm 01:25 PM

隨著網路的普及,越來越多的網站提供了圖片、影片等資源的外鏈功能。然而,這種外鏈功能卻容易被偷鏈。盜鍊是指其它網站利用你網站上的圖片、影片等資源,直接透過引用地址在自己的網站顯示這些資源,而不是將其下載到自己的伺服器上。這樣一來,盜鏈網站就可以免費使用你網站的流量和頻寬資源,這既浪費資源又影響網站速度。針對這種問題,可以使用Nginx進行防盜鏈。 Nginx是

用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_

如何使用C++中的分治演算法 如何使用C++中的分治演算法 Sep 20, 2023 pm 03:19 PM

如何使用C++中的分治演算法分治演算法是一種將問題分解成若干個子問題,再將子問題的解合併起來得到原問題解的方法。它的應用廣泛,可以用來解決各種類型的問題,包括數學問題、排序問題、圖表問題等等。本文將介紹如何使用C++中的分治演算法,並提供具體的程式碼範例。一、基本思想分治演算法的基本思想是將一個大問題分解成若干個規模較小的子問題,對每個子問題進行遞歸求解,最後合併子問題

See all articles