ホームページ ウェブフロントエンド jsチュートリアル diffアルゴリズムの使い方を詳しく解説(コード付き)

diffアルゴリズムの使い方を詳しく解説(コード付き)

May 08, 2018 am 09:20 AM
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判定( unknown か null か)

  • // 空のマウント(おそらくコンポーネント)、新しいルート要素を作成createElm(vnode,insertVnodeQueue) ここで、ノードの作成が1つも挿入されていないことがわかります。 1 つですが、統合バッチ処理のためにキューに入れられます

  • コア関数 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)
   )
  )
 )
}
ログイン後にコピー
これは、2 つのノードのキー、タグ (ラベル)、およびデータを直接比較する外部比較関数です (ここでのデータは VNodeData を指します)、入力タイプを直接比較します。

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;
}
ログイン後にコピー
これにより、2 つのノードにさらに比較値があるかどうかが確認され、そうでない場合は直接置換されます

置換プロセスは主に createElm 関数であり、もう 1 つは古い VNode を破棄することです

// destroy old node
    if (isDef(parentElm)) {
     removeVnodes(parentElm, [oldVnode], 0, 0)
    } else if (isDef(oldVnode.tag)) {
     invokeDestroyHook(oldVnode)
    }
ログイン後にコピー
挿入プロセスは単に、ノードのタイプを指定して個別に呼び出します

createComponent (子があるかどうかを判断して再帰的に呼び出します)

createComment

createTextNode

作成後に挿入関数を使用します

その後、ハイドレート関数を使用する必要があります仮想 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)
   }
  }
 }
ログイン後にコピー

core function

 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, '')
    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, '')
   }
  } 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 を参照するようにします。が変更されると、vnode.el も同期的に変更されます。

  1. 2つの参照が一致しているか比較してください

  2. その後asyncFactoryが何をするのか分からないので理解するのが難しいです

  3. 静的ノードはキーが同じであれば比較します。再レンダリングはされませんが、componentInstance を直接コピーします (コマンドがここで有効になると)

  4. vnode がテキスト ノードまたは

    annotation ノードであるが、vnode.text != oldVnode.text の場合は、更新するだけで済みます。 vnode.elm のテキストコンテンツ

  5. children's Compare

  • oldVnode のみに子ノードがある場合は、これらのノードを削除します

  • vnode のみに子ノードがある場合は、これらの子ノードを作成します (oldVnode の場合はここ)がテキストノードである場合、vnode.elm を配置します。テキストが空の文字列に設定されている場合

  • 。これについては後で詳しく説明します
  • oldVnode にも vnode にも子ノードがない場合、oldVnode は更新されます。テキスト ノードまたはコメント ノードの場合は、vnode.elm を追加します。テキストは空の文字列に設定されます
updateChildren

この部分の焦点は依然としてアルゴリズム全体にあります

最初の 4 つのポインター、oldStart、oldEnd 、newStart、newEnd、2 つの配列、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)
  }
 }
ログイン後にコピー

ループ比較のいくつかの状況と処理 (以下の ++ -- すべてインデックスの ++ を参照します --) 比較は比較対象のノードです。この比較では、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++
  5. の 2 つのプロセスがあります。
  • 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数组中,多退少补而已

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

推荐阅读:

pushState与replaceState使用步骤详解

CSS3二级导航菜单制作步骤详解

以上がdiffアルゴリズムの使い方を詳しく解説(コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットな記事タグ

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

ブルースクリーンコード0x0000001が発生した場合の対処方法 ブルースクリーンコード0x0000001が発生した場合の対処方法 Feb 23, 2024 am 08:09 AM

ブルースクリーンコード0x0000001が発生した場合の対処方法

Win11での管理者権限の取得について詳しく解説 Win11での管理者権限の取得について詳しく解説 Mar 08, 2024 pm 03:06 PM

Win11での管理者権限の取得について詳しく解説

コード0xc000007bエラーを解決する コード0xc000007bエラーを解決する Feb 18, 2024 pm 07:34 PM

コード0xc000007bエラーを解決する

ブルー スクリーン コード 0x000000d1 は何を表しますか? ブルー スクリーン コード 0x000000d1 は何を表しますか? Feb 18, 2024 pm 01:35 PM

ブルー スクリーン コード 0x000000d1 は何を表しますか?

あらゆるデバイス上の GE ユニバーサル リモート コード プログラム あらゆるデバイス上の GE ユニバーサル リモート コード プログラム Mar 02, 2024 pm 01:58 PM

あらゆるデバイス上の GE ユニバーサル リモート コード プログラム

Oracle SQLの除算演算の詳細説明 Oracle SQLの除算演算の詳細説明 Mar 10, 2024 am 09:51 AM

Oracle SQLの除算演算の詳細説明

PHPモジュロ演算子の役割と使い方を詳しく解説 PHPモジュロ演算子の役割と使い方を詳しく解説 Mar 19, 2024 pm 04:33 PM

PHPモジュロ演算子の役割と使い方を詳しく解説

Linuxシステムコールsystem()関数の詳細説明 Linuxシステムコールsystem()関数の詳細説明 Feb 22, 2024 pm 08:21 PM

Linuxシステムコールsystem()関数の詳細説明

See all articles