この記事では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. if ( oldVnode.key === newVnode.key && oldVnode.sel === newVnode.sel) {
  10. //同じノードの場合は、精密な比較を実行します
  11. パッチVnode(古いVnode、新しいVnode);
  12. }
  13. それ以外{
  14. // 同じノードではないので、新しいノードを強制的に挿入し、古いノードを削除します
  15. newVnodeElm = createElement(newVnode); を作成します。
  16.  
  17. // 新しいノードを古いノードの前に挿入します
  18. if (oldVnode.elm.parentNode && newVnodeElm) {
  19. oldVnode.elm.parentNode.insertBefore(newVnodeElm, oldVnode.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. oldVnode.elm.innerText = newVnode.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. while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  22. // undefined でマークされたコンテンツをスキップします
  23. if (oldStartVnode == null || oldCh[oldStartIdx] === 未定義) {
  24. oldStartVnode = oldCh[++oldStartIdx];
  25. }
  26. そうでない場合 (oldEndVnode == null || oldCh[oldEndIdx] === 未定義) {
  27. oldEndVnode = oldCh[ --oldEndIdx];  
  28. }
  29. else if (newStartVnode == null || newCh[newStartIdx] === 未定義) {
  30. newStartVnode = newCh[++newStartIdx];
  31. }
  32. else if (newEndVnode == null || newCh[newEndIdx] === 未定義) {
  33. newEndVnode = newCh[ --newEndIdx];  
  34. }
  35. else if (checkSameVnode(oldStartVnode, newStartVnode)) {
  36. // 新しいフロントと古いフロント
  37. console.log( '新しいフロントと古いフロントがヒットしました' );
  38. patchVnode(oldStartVnode, newStartVnode);
  39. oldStartVnode = oldCh[++oldStartIdx];
  40. newStartVnode = newCh[++newStartIdx];
  41. }
  42. else if (checkSameVnode(oldEndVnode, newEndVnode)) {
  43. // 新旧の女王
  44. console.log( '新しいヒットと古いヒット' );
  45. パッチVnode(古いEndVnode、新しいEndVnode);
  46. oldEndVnode = oldCh[ --oldEndIdx];  
  47. newEndVnode = newCh[ --newEndVnode];  
  48. }
  49. else if (checkSameVnode(oldStartVnode, newEndVnode)) {
  50. console.log( 'ヒット後の新しい値とヒット前の古い値' );
  51. patchVnode(oldStartVnode, newEndVnode);
  52. // 新しい背面が古い前面に当たったとき、ノードを古いノードの背面に移動します。
  53. parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling);
  54. oldStartVnode = oldCh[++oldStartIdx];
  55. newEndVnode = newCh[ --newEndIdx];  
  56. }
  57. else if (checkSameVnode(oldEndVnode, newStartVnode)) {
  58. // 新しい前と古い後
  59. console.log( 'ヒット前の新しい値とヒット後の古い値' );
  60. パッチVnode(古い終了Vnode、新しい開始Vnode);
  61. // 新しいフロントと古いバックがヒットすると、ノードを移動する必要があり、新しいフロントが指すノードは古いノードの前面に移動されます。
  62. parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
  63. oldEndVnode = oldCh[ --oldEndIdx];  
  64. newStartVnode = newCh[++newStartIdx];
  65. }
  66. それ以外{
  67. // 4つのヒットのいずれも
  68. // 毎回古いオブジェクトを走査しなくても済むように、keyMap マッピング オブジェクトを作成します
  69. キーマップの場合
  70. キーマップ = {};
  71. for (let 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. parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm);
  83. }
  84. それ以外{
  85. // 未定義でない場合は、新しい項目ではないので移動する必要があります
  86. elmToMove は oldCh[idxInOld] を継承します。
  87. elmToMove に新しい StartVnode を追加します。
  88. // この項目を未定義に設定し、この項目が処理されたことを示します
  89. oldCh[idxInOld] = 未定義;
  90. // 動く
  91. parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm);
  92. }
  93. // ポインタを下に移動し、新しいヘッドのみを移動します
  94. newStartVnode = newCh[++newStartIdx];
  95. }
  96. }
  97.  
  98. // ループが終了したら、未処理の項目を処理する
  99. if (newStartIdx <= newEndIdx) {
  100. console.log( 'new にはまだ処理されていないノードが残っています。項目を追加し、残りのすべてのノードを oldStartIdx の前に挿入します' );
  101. // 新しい newCh を走査し、まだ処理されていない古いものに追加します
  102. for (let i = newStartIdx; i <= newEndIdx; i++) {
  103. // insertBefore メソッドはnull を自動的に識別し nullの場合は自動的にキューの末尾に配置されます。
  104. // newCh[i]はまだ実際のDOMを持っていないので、createElement関数を呼び出してDOMにします
  105. parentElm.insertBefore(createElement(newCh[i]), oldCh[oldStartIdx].elm);
  106. }
  107. }
  108. else if (oldStartIdx <= oldEndIdx) {
  109. console.log( 'old にはまだ処理されていないノードが残っているので、その項目を削除する必要があります' );
  110. // oldStart と oldEnd ポインタ間の項目を一括削除します
  111. for (let 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特許出願の主な発信源となっている

<<:  1分でDockerを使って新しいSentry-CLIを使い始める - バージョンを作成する

>>:  クラウド データベースの選択に必読: 自分に合ったものが必ず見つかります!

推薦する

検察はQQアカウント盗難と詐欺の背後にある闇産業チェーンを暴露

まずQQアカウントを盗み、次にアカウント所有者になりすましてQQの友人からお金を借り、さらには「身元...

Baiduがオリジナルコンテンツと転載コンテンツを区別する方法を分析

SEO にとって、ソフト記事は非常に効果的なプロモーション方法であるだけでなく、外部リンクを増やすた...

Doumi の「Good Job」マーケティングのレビュー: 視聴者獲得競争からマインド獲得まで、ブランドはどのようにして中心的地位を維持できるのか?

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っています誰もが良い...

ウェブマスターはどのようにして優れた外部リンク リソースを見つければよいでしょうか?

インターネットには外部リンクの作り方に関する記事がたくさんありますが、どれも同じです。フォーラム、機...

実用的なヒント:商人が望む618のテキストメッセージを送信するための完全なガイド

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っています618 電...

外部リンクの数はウェブサイトのランキングを測定する上で必須の要素ではない

SEO について知っている友人の多くは、「コンテンツは王様、外部リンクは女王」ということわざを知って...

モバイル検索が今後も発展を続けていくためには、どこに重点を置くべきでしょうか?

最近の関連データによると、デスクトップ検索のリーダーである百度は、モバイル検索でも依然として強い地位...

モバイルインターネットに検索エンジンは本当に必要ですか?

厳密に言えば、検索エンジンとは、特定の戦略に基づいて特定のコンピュータプログラムを使用してインターネ...

#Black5# mxroute: 郵便サービス、年間 15 ドル、1 時間あたり 1 メールボックスあたり 300 通のメール

に設立された企業である mxroute は、有料の郵便局サービスを主に提供しており、独自の AS39...

中国の SaaS は終焉を迎えたのか?

最近、中国のいくつかのスターSaaS企業が「困難な状況」にあると報告している。総合労務管理サービスプ...

クラウド戦争が中盤に突入する中、勝利の要因は何になるでしょうか?

テンセントは、C2Bを重視した通常のエコロジカルなアプローチを採用し、非常に速いスピードで注文を受け...

ウェブサイトのタイトルを変更した後、重みとランキングを素早く復元する方法

時には、ウェブサイトのタイトルを変更しなければならないと本当に思うこともありますが、他の人が共有した...

SEO の犠牲になった人は何人いるでしょうか?

SEO 初心者、あるいは永遠に SEO 初心者である人々がいます。彼らは目覚めない限り、決して成長で...

3つの主流の分散トランザクションソリューションの長所と短所の詳細な説明

[[281095]] 1. 分散トランザクションへの序章トランザクション: トランザクションは、一連...