この記事ではDiffアルゴリズムの使い方を説明します

この記事ではDiffアルゴリズムの使い方を説明します

[[420540]]

1. 基本

Diff アルゴリズムは、仮想 DOM の最小限の更新を実装します。この文は短いですが、仮想 DOM と最小限の更新という 2 つのコア要素が含まれています。

1. 仮想DOM

仮想 DOM とは、実際の DOM ツリーを js オブジェクトの形式で構築することを指し、それによって実際の DOM を操作するブラウザーのパフォーマンスの問題を解決します。

例えば、次のDOMと仮想DOMのマッピング関係

2. 最小限の更新

Diff の目的は、新しい仮想 DOM と古い仮想 DOM の間で更新された最小の部分を見つけ、その部分に対応する DOM を更新することです。

2. 全体のプロセス

Diff アルゴリズムは本当に美しいです。全体のプロセスは以下の図に示されています。

  1. まず、新旧のノードが同じノードであるかどうかを比較します(sel(セレクタ)とkey(一意の識別子)の値が同じかどうかを比較します)。同じノードでない場合は、強制削除が行われます(注:最初に古いノードに基づいて新しいノードを挿入し、次に古いノードを削除します)。
  2. 同じノードの場合は、さらに比較する必要があります

まったく同じ、加工なし

新しいノードコンテンツはテキストなので、置き換えるだけです

新しいノードには子ノードがあるため、この時点では慎重に検討する必要があります。古いノードに子要素がない場合は、古いノードをクリアして新しいノードの子要素を挿入するだけです。古いノードに子要素がある場合は、上記の更新戦略に従って解決する必要があります (更新戦略を覚えておけば、あと数年間は自慢できます、666666)。

3. 実戦

実践のない話ばかりです。以下は diff アルゴリズムの核となる内容です。

3.1 パッチ機能

Diff アルゴリズムのエントリ関数は、主に新しいノードと古いノードが同じノードであるかどうかを判断し、それらを異なるロジックに渡して処理します。

  1. エクスポートデフォルト 関数patch(oldVnode, newVnode) {
  2. // 渡された最初のパラメータがDOMノードか仮想ノードかを判断する
  3. oldVnode.sel === '' || oldVnode.sel === 未定義の場合{
  4. // 渡される最初のパラメータはDOMノードであり、仮想ノードにパッケージ化する必要があります
  5. oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], 未定義, oldVnode);
  6. }
  7.  
  8. // oldVnode と newVnode が同じノードであるかどうかを判定する
  9. (oldVnode.key === newVnode.key && oldVnode.sel === newVnode.sel)の場合{
  10. //同じノードの場合は、精密な比較を実行します
  11. パッチVnode(古いVnode、新しいVnode);
  12. }
  13. それ以外{
  14. // 同じノードではないので、新しいノードを強制的に挿入し、古いノードを削除します
  15. newVnodeElm = createElement(newVnode); を作成します。
  16.  
  17. // 新しいノードを古いノードの前に挿入します
  18. (古いVnode.elm.parentNode && 新しいVnodeElm) の場合 {
  19. 古いVnode.elm.parentNode.insertBefore(新しいVnodeElm、古いVnode.elm);
  20. }
  21. // 古いノードを削除する
  22. oldVnode.elm.parentNode.removeChild(oldVnode.elm);
  23. }
  24. }

3.2 patchVnode関数

この機能は主に、精緻な比較を行う機能です。上記のフローチャートのロジックに従って、メッセージがどのブランチに属するかを判断し、異なる処理ロジックを採用します。 (アイデアは明確で、アルゴリズムは素晴らしいです)

  1. エクスポートデフォルト 関数patchVnode(oldVnode, newVnode) {
  2. // 新しいvnodeと古いvnodeが同じオブジェクトであるかどうかを判定する
  3. (古いVnode === 新しいVnode)の場合{
  4. 戻る;
  5. }
  6. // vnode にテキスト属性があるかどうかを判定する
  7. newVnode.text !== 未定義 && (newVnode.children === 未定義 || newVnode.children.length === 0) の場合 {
  8. console.log( '新しい vnode にはテキスト属性があります' );
  9. (新しいVnode.text !== 古いVnode.text) の場合 {
  10. 古いVnode.elm.innerText = 新しいVnode.text;
  11. }
  12. }
  13. それ以外{
  14. // 新しいvnodeにはテキスト属性はありませんが、子ノードがあります
  15. console.log( '新しい vnode にはテキスト属性がありません' );
  16. // 古いものに子があるかどうかを判定する
  17. oldVnode.children !== 未定義 && oldVnode.children.length > 0 の場合 {
  18. // 古いものには子があり、新しいものにも子があります
  19. 古いVnode.elm、新しいVnode.childrenを更新します。
  20. }
  21. それ以外{
  22. // 古いものには子がなく、新しいものには子があります
  23. // 古いノードの内容をクリアする
  24. oldVnode.elm.innerHTML = '' ;
  25. // 新しい vnode の子ノードを走査し、DOM を作成し、ツリーを上に進みます
  26. ( i = 0 とします; i < newVnode.children.length; i++) {
  27. dom = createElement(newVnode.children[i]); を作成します。
  28. 古いVnode.elm.appendChild(dom);
  29. }
  30. }
  31. }
  32. }

3.3 updateChildren関数

コア関数は、主に古い仮想ノードと新しい仮想ノードの両方に子要素がある状況を担当し、比較戦略に従ってそれらを順番に比較し、最終的に子要素内の変更された部分を見つけて、最小限の更新を実現します。この部分には、次のようないくつかの指針があります。

  1. 古いフロントは、更新前の仮想DOMのヘッドポインタを参照します。
  2. 古いバックは、更新前の仮想DOMの末尾ポインタを参照します。
  3. 新しいフロントは、更新された仮想DOMのヘッドポインタを参照します。
  4. 新しいものは更新された仮想DOMの末尾ポインタを参照します

上記の更新戦略に従って、古い仮想 DOM を新しい仮想 DOM に更新するプロセスは次のようになります。

  1. 「新しい後、古い後」戦略がヒットし、その後、文字後の DOM ノード (つまり、ノード 1) が古い後ノード (ノード 3) の後ろに移動し、その後、古い前ノード ポインターが下に移動し、新しい後ノード ポインターが上に移動します。
  2. 「新しい後ろ、古い前」戦略は依然としてヒットし、同じ操作を実行して、ノード 2 を古い後ろのノード (ノード 3) の後ろに移動し、古い前部のノードを下に移動し、新しい後ろのノードを上に移動します。
  3. 「新しいフロント、古いフロント」戦略がヒットすると、DOM ノードは変更されず、古いフロント ノードと新しいフロント ノードの両方が下に移動します。
  4. ループから飛び出すと動きは終了します。
  1. エクスポートデフォルト 関数updateChildren(parentElm, oldCh, newCh) {
  2. // 古いフロント
  3. oldStartIdx を 0 にします。
  4. // 新しいフロント
  5. newStartIdx = 0 とします。
  6. // 古い
  7. oldEndIdx = oldCh.length - 1 とします。
  8. // 新しい
  9. newEndIdx = newCh.length - 1 とします。
  10. // 古いフロントノード
  11. oldStartVnode = oldCh[0]とします。
  12. // 古い投稿ノード
  13. oldEndVnode = oldCh[oldEndIdx]とします。
  14. // 新しいフロントノード
  15. newStartVnode = newCh[0]とします。
  16. // 新しい投稿ノード
  17. newEndVnode = newCh[newEndIdx]とします。
  18.  
  19. keyMap をnullにします
  20.  
  21. (古い開始Idx <= 古い終了Idx && 新しい開始Idx <= 新しい終了Idx) {
  22. // undefined でマークされたコンテンツをスキップします
  23. (oldStartVnode == null || oldCh[oldStartIdx] === 未定義)の場合{
  24. 古いStartVnode = 古いCh[++古いStartIdx];
  25. }
  26. そうでない場合 (oldEndVnode == null || oldCh[oldEndIdx] === 未定義) {
  27. oldEndVnode = oldCh[ --oldEndIdx];  
  28. }
  29. そうでない場合 (newStartVnode == null || newCh[newStartIdx] === 未定義) {
  30. 新しいStartVnode = 新しいCh[++新しいStartIdx];
  31. }
  32. そうでない場合 (newEndVnode == null || newCh[newEndIdx] === 未定義) {
  33. 新しいEndVnode = 新しいCh[ --newEndIdx];  
  34. }
  35. そうでない場合 (checkSameVnode(oldStartVnode, newStartVnode)) {
  36. // 新しいフロントと古いフロント
  37. console.log( '新しいフロントと古いフロントがヒットしました' );
  38. パッチVnode(古い開始Vnode、新しい開始Vnode);
  39. 古いStartVnode = 古いCh[++古いStartIdx];
  40. 新しいStartVnode = 新しいCh[++新しいStartIdx];
  41. }
  42. そうでない場合 (checkSameVnode(oldEndVnode, newEndVnode)) {
  43. // 新旧の女王
  44. console.log( '新しいヒットと古いヒット' );
  45. パッチVnode(古いEndVnode、新しいEndVnode);
  46. oldEndVnode = oldCh[ --oldEndIdx];  
  47. 新しいEndVnode = newCh[ --newEndVnode];  
  48. }
  49. そうでない場合 (checkSameVnode(oldStartVnode, newEndVnode)) {
  50. console.log( 'ヒット後の新しい値とヒット前の古い値' );
  51. パッチVnode(古い開始Vnode、新しい終了Vnode);
  52. // 新しい背面が古い前面に当たったとき、ノードを古いノードの背面に移動します。
  53. 親Elm.insertBefore(oldStartVnode.elm、oldEndVnode.elm.nextSibling);
  54. 古いStartVnode = 古いCh[++古いStartIdx];
  55. 新しいEndVnode = 新しいCh[ --newEndIdx];  
  56. }
  57. そうでない場合 (checkSameVnode(oldEndVnode, newStartVnode)) {
  58. // 新しい前と古い後
  59. console.log( 'ヒット前の新しい値とヒット後の古い値' );
  60. パッチVnode(古い終了Vnode、新しい開始Vnode);
  61. // 新しいフロントと古いバックがヒットすると、ノードを移動する必要があり、新しいフロントが指すノードは古いノードの前面に移動されます。
  62. 親Elm.insertBefore(oldEndVnode.elm、oldStartVnode.elm);
  63. oldEndVnode = oldCh[ --oldEndIdx];  
  64. 新しいStartVnode = 新しいCh[++新しいStartIdx];
  65. }
  66. それ以外{
  67. // 4つのヒットのいずれも
  68. // 毎回古いオブジェクトを走査しなくても済むように、keyMap マッピング オブジェクトを作成します
  69. キーマップの場合
  70. キーマップ = {};
  71. ( i = oldStartIdx; i <= oldEndIdx; i++ とします) {
  72. 定数キー= oldCh[i] .key ;
  73. if (キー!== 未定義) {
  74. keyMap[キー] = i;
  75. }
  76. }
  77. }
  78. // keyMap 内の現在の項目 (newStartIdx) のマッピング位置を検索します
  79. const idxInOld = keyMap [newStartVnode.key ];
  80. idxInOld === 未定義の場合{
  81. // idxInOld が未定義の場合は、まったく新しいアイテムを意味します。このとき、アイテムは DOM ノードとして作成され、古いアイテムの前に挿入されます。
  82. 親Elm.insertBefore(createElement(newStartVnode)、oldStartVnode.elm);
  83. }
  84. それ以外{
  85. // 未定義でない場合は、新しい項目ではないので移動する必要があります
  86. elmToMove は、古い Ch[idxInOld] を移動します。
  87. elmToMove に新しい StartVnode を追加します。
  88. // この項目を未定義に設定し、この項目が処理されたことを示します
  89. oldCh[idxInOld] = 未定義;
  90. // 動く
  91. 親Elm.insertBefore(elmToMove.elm、oldStartVnode.elm);
  92. }
  93. // ポインタを下に移動し、新しいヘッドのみを移動します
  94. 新しいStartVnode = 新しいCh[++新しいStartIdx];
  95. }
  96. }
  97.  
  98. // ループが終了したら、未処理の項目を処理する
  99. (新しい開始ID <= 新しい終了ID) の場合 {
  100. console.log( 'new にはまだ処理されていないノードが残っています。項目を追加し、残りのすべてのノードを oldStartIdx の前に挿入します' );
  101. // 新しい newCh を走査し、まだ処理されていない古いものに追加します
  102. (i = newStartIdx; i <= newEndIdx; i++)の場合{
  103. // insertBefore メソッドはnull を自動的に識別し nullの場合は自動的にキューの末尾に配置されます。
  104. // newCh[i]はまだ実際のDOMを持っていないので、createElement関数を呼び出してDOMにします
  105. 親Elm.insertBefore(createElement(newCh[i]), oldCh[oldStartIdx].elm);
  106. }
  107. }
  108. そうでない場合 (oldStartIdx <= oldEndIdx) {
  109. console.log( 'old にはまだ処理されていないノードが残っているので、その項目を削除する必要があります' );
  110. // oldStart と oldEnd ポインタ間の項目を一括削除します
  111. (i = oldStartIdx; i <= oldEndIdx; i++)の場合{
  112. もし (oldCh[i]) {
  113. 親Elm.removeChild(oldCh[i].elm);
  114. }
  115. }
  116. }
  117. }

【編集者のおすすめ】

  1. 鴻蒙公式戦略協力と建設——HarmonyOS技術コミュニティ
  2. なぜ MQ はインターネット アーキテクチャの分離アーティファクトであると言われるのでしょうか?
  3. Prometheus アラームルール管理
  4. 最高裁判所、人力資源・社会保障省:「996」は重大な法律違反です!貴社では「996」のキャンセルを議題に上げていらっしゃいますか?
  5. Python が C 言語に正面から挑んだら何が起こるでしょうか?
  6. CNNIC: 私の国は6G特許出願の主な発信源となっている

<<:  人工知能とコンピュータービジョンの違いは何ですか?

>>:  猿人歩行からAIまで:三次元戦略で一人ひとりに寄り添う「真のセキュリティ」

ブログ    
ブログ    
ブログ    

推薦する

ロボット対コンベア:倉庫物流における戦い

ありがたいことに、倉庫のピッキング作業で荷物を手作業で扱う時代は終わりつつあります。コンベアを使用す...

ChatGPTメジャーアップデート!新しい API 関数呼び出し、コンテキストが 4 倍に急増、価格が下落

ビッグデータダイジェスト制作ただ! OpenAI は GPT シリーズのメジャーアップデートをリリー...

世界で最も難しい「砂の彫刻」ゲームがAIによって解読された

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

JDデジタルJDDコンペティションの優勝者が発表されました

現在、私たちはAIとビッグデータの急速な発展の時期を迎えています。これらの最先端技術は産業界に力を与...

人類はついに怠惰なAIを生み出してしまった…

強化学習 (RL) の概念を説明する記事は多数ありますが、現実世界で RL を実際に設計して実装する...

...

汎用人工知能の実現に私たちはどれくらい近づいているのでしょうか?

今日、人工知能は人間が行う作業の一部をより良く行うために懸命に取り組んでいます。たとえば、AI は人...

AIを活用してよりスマートな電子データ交換を実現

電子データ交換 (EDI) の歴史は、企業がより効率的に電子的にデータを交換する方法を模索し始めた ...

知らないうちに個人のプライバシーを人工知能に「提供」しないでください

[[260334]] BBCによると、IBMは最近、顔認識アルゴリズムの訓練のため、ユーザーの同意を...

...

インテルのAIが破壊された万里の長城の修復にどのように貢献したか、その背後にある秘密が発見された

人工知能がテクノロジーと人文科学の交差点に到達したとき、どのようなエネルギーが解き放たれるのでしょう...

ResNet仮説は覆されたか? Redditの人:長年誰もその原理を理解していなかった

[[429626]] 2015 年に、画期的なニューラル ネットワーク モデル ResNet がリ...

...

美団におけるナレッジグラフ可視化技術の実践と探究

著者 | 魏耀成魏ナレッジ グラフの視覚化により、ナレッジ グラフ データをより直感的に表示および分...