snabbdomパッチ関数の簡単な実装diffアルゴリズムの洗練された比較実装

前回の記事のDiffアルゴリズムの鍵から、パッチ関数が仮想ノードをDOMツリーに更新する方法を見てみましょう。そして、diffアルゴリズムを実行する方法。

写真を見てください、ここに画像の説明を挿入します
あなたはソースコードを見ることができます:
ここに画像の説明を挿入します

パッチのシンプルバージョンを完成させる

最初に基本的な形状を見てください
ここに画像の説明を挿入します
ここに画像の説明を挿入します

ここに画像の説明を挿入します
ここに画像の説明を挿入します
仮想ノードの子が配列の場合、createElementを再帰的に呼び出し続ける必要があり、作成するcreateElementには2番目のパラメーターDOMノードが必要です。再帰中にこのノードはありません。 。1つずつ直接追加されるため、改造する必要があります。
ここに画像の説明を挿入します
ここに画像の説明を挿入します
外でそれを扱ってください。
再帰的な場合は、CreateELementのツリーに直接移動できます

ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
普通に木に登ります。

パッチ関数の単純なツリーアップは、実際にはReactDom.render関数に似ています。これは、プロセスを実行する子があるかどうかを判断し、子がある場合は繰り返します。

異なるノードが激しく削除されているかどうかを確認した後

ここに画像の説明を挿入しますここに画像の説明を挿入します

異なるノードの置き換えが完了しました。これで、最も難しいdiffアルゴリズムの改良比較部分を開始する準備が整いました。

洗練された比較

最初に画像を押します。
ここに画像の説明を挿入します
「はい」の
場合、同じノードである場合は、同じオブジェクトであるかどうかを判断します。次に、新しいノードにテキストとノードがあるかどうかを判断します。ある場合は、子がないことを意味し(子またはテキストのみを渡すことができると想定した低バージョン)、古いノードのテキストがと同じであるかどうかを判断します。新しいノードで、同じでない場合は、古いノードのテキストが新しいノードのテキストと異なるか、古いノードに子があることを意味します。この時点で、新しいノードのテキストを直接置き換えます。古いノードのコンテンツで。テキストと子の両方が置き換えられます。
次に、新しいノードにテキストがない場合は、子があることを意味します。古いノードに子があるかどうかを判断する場合、子がある場合は、これが最も複雑な操作です。そうでない場合は、簡単です。古いノードのtxtを激しく削除してから、新しいノードのDOMを挿入します。

まず、簡単な2段階の操作を完了しましょう。
ここに画像の説明を挿入します
新しいノードのテキストが古いノードと異なる場合は、直接置き換えます。
ここに画像の説明を挿入します
次に、新しいノードがテキストでない場合は、子があることを示します。次に、古いノードに子がなく
ここに画像の説明を挿入します
テキストがある場合は、テキスト値を直接削除してから、ノードを再帰的に作成し、古いノードのDOMに配置します。

次に、最も複雑な場所、つまり両方に子があります。ここに画像の説明を挿入します
ロジックを見つけましたかつまり、そのコンテンツがレイヤーごとに徐々に表示される場合があります。つまり、1つのレイヤーに1つのレイヤーがあり、1つのレイヤーに別のレイヤーがあります。そのため、判断する際には、各層で再判断する必要があります。古いノードがDOMである最初から、h関数が含まれるまで判断しており、再帰的に判断する必要があります。

3つの状況があり、最初の状況は直接増加します

ここに画像の説明を挿入します

2番目のタイプの更新は、AがCになることです。

3番目のタイプは削減です。たとえば、Bを削除します。

ほとんどの人は、最初のケースのように、2つのforループを使用して2人の子供(私を含む)の変化を比較することがあります

ここに画像の説明を挿入します
たとえば、ABに移動すると、3番目が異なります。Cの前に挿入するのが論理的であるため、ポインタを使用して古い子をポイントします。は同じノードです。追加を続けます。そうでない場合は、今追加するノードが、古い子の未処理のノードの前にある必要があることを証明します。次に、ポインタが終了すると、次に作成するノードが子の背後にあることを示しているので、直接追加します。
しかし、2番目と3番目の単語は書きにくいです。この種のアルゴリズムは低すぎます。次に、より実用的なものを見てみましょう。

次は、4つのポインターを移動するDIFFアルゴリズムの更新戦略です。それはまた、インタビュアーがよく尋ねるのも好きです。

言語の説明が明確ではないため、ステーションBに移動してdiffアルゴリズムを確認することをお勧めします。
これはURLです:https://www.bilibili.com/video/BV1v5411H7gZ?p = 12&spm_id_from = pageDriver
ここに要約があります。
4つのポインター。それらは新しい前、新しい後、古い前、古い後です。ソースコードを見ることができます。これ
ここに画像の説明を挿入します
は新しいこと意味します。

新しいフロントは、新しいノードの最初のノードを指します。

新しいノードの後の新しいノードの最後のノード

古いノードの前の古いノードへの最初のポインタ。

古い投稿は、古いノードの最後のポインターを指しています。

判定方法は全部で4つあります。
それぞれ

新しいフロントと古いフロント

新女王と旧女王

新しい後と古い前

新しい前と古い後。

合計6つのケースがあります。最初の4つはforループを追加することで検出されますが、2つはforループでは検出されません。

つまり、新しいフロントポインタが新しいバックポインタと比較され、同じノードの場合は、一緒に次のノードを指します。
同様に、新しい投稿を古い投稿と比較すると、同じノードであり、前のノードを一緒に指します。
新しいバックと古いフロントが同じノードにある場合、新しいバックが上がり、古いフロントが下がります。
新しいフロントと古いバックが同じノードの場合、新しいフロントがダウンし、古いバックがダウンします。
4つがない場合は、forループを使用します。forループとしてはカウントされません。関数は内部的に呼び出され、キー値とインデックス値に基づいてオブジェクトを生成します。オブジェクト内のデータは、新しいポインタのキー値を介して直接取得されます。undefinedが返された場合、それは6番目のタイプであり、見つかりません。
見つかった場合は、forループで見つかった5番目のタイプです。

各ノードを相互に比較する場合、常に新しいフロントと古いフロントから開始し
、最後のフロントと比較することを忘れないでください
ループはいつ終了しますか?
つまり、新しいフロントが新しいバック以下で、古いフロントが古いバック以下の場合、サイクルが続行されます。
新しいノードのポインタが最初に終了するとき、つまり新しいフロントが新しいリアよりも大きい場合、古いフロントと古いリアに含まれるノードを削除する必要があります。
古いノードのポインタが最初に終了するとき、つまり古いフロントが古いバックよりも大きい場合、新しいフロントと新しいバックに含まれるノードが最後に追加されます。
3番目と4番目のケースは追加で処理する必要があります。
3つ目は、新しいバックが古いフロントと比較され、一致すると、新しいフロントが指すノードが古いバックの後のノードに移動されることです。次に、それぞれ上下に移動します。
4つ目は、新しいフロントと古いバックが一致する場合、新しいフロントが指すノードを古いフロントの前の位置に配置してから、上下に移動する必要があることです。
forループの場合は1つだけです。つまり、4つが一致しない場合、つまり、新しいフロントが古いリアと一致しないと判断された場合、forループが実行されます。forループは、古いフロントと古いバックの間の
一種のループです。これは、古いノードからforまでにあり、新しいフロントノードが古いフロントの前に実際のDOMに挿入され、古いノードが照合されます。 toノードはundefinedに設定されています。次に、新しいフロントを約束し、古いフロントを古いフロントの前に移動しないでください。
1つは見つからない場合です。新しいフロントポインティングノードを古いフロントの前に置き、新しいフロントを歩き続けます。一致するものがないため、古い前面または古い背面を移動しないでください。
ここに画像の説明を挿入します
ソースコードもこのタイプの比較であることがわかります。

最初の4つのタイプで最初に書くことができます

ここに画像の説明を挿入します
最初に
ここに画像の説明を挿入します
最初のタイプを周期的に判断し、新しいタイプの前に、古いタイプの前に、これの微妙な点は、patchVnodeが呼び出され、次にノードが処理され、古いノードの値が新しいノードに更新されることです。 。比較された2つのノードに子がある場合、upDateChildrenはpatchVnodeで呼び出され、相互に呼び出します.2つのノードの子のみが引き続きupDateChildrenを呼び出すため、子でなくなるまでトラバースされて呼び出されます。ポインタを移動することを忘れないでください。
ここに画像の説明を挿入します
2つ目は、patchVnodeを使用して、ノードが一致したときにノードを処理することです。つまり、最小の絞り込み比較であり、操作は古いノードで実行されます。
ここに画像の説明を挿入します
古いものよりも新しいものに注意してください。ノード操作を実行する必要があります。これは、同じノードであり、古いDOM要素と新しいDOM要素が同じである、つまりELMが同じであり、子が異なるためです。PatchVnodeは、テキストの変更など、新しいノードの変更を古いノードに反映します。これは、patchVnodeが古いノードのDOMを操作してから、古いノードを古いノードの前面と背面に挿入するためです。古いノードがpatchVnodeになった後は、子も新しいノードと同じであるため、ノードは古いノードのすぐ後ろに挿入されます。
ここに画像の説明を挿入します

新しいものの前後の考え方は同じです。

次に、whileからジャンプするときに、forループが後で書き込まれることを記述します。

ここに画像の説明を挿入します
これは比較的簡単です。新しいものが最初に終了する場合は、古いものを直接トラバースして削除できます。古いものが最初に終了する場合は、それらの新しいものを直接トラバースして追加できます。ここでは、Baiduにアクセスする方法がわからない場合に、フラグメントを使用してDOM操作を減らし、フラグメント化ストリームオブジェクトをドキュメント化します。

次に、最も複雑なものを書きます

これはforループ
であり、ソースコードを見ることができます

ここに画像の説明を挿入します
ここに画像の説明を挿入します
これは、キー値を格納することです。つまり、古いものの前後に古いものに含まれているKey値を取り出して、毎回移動するのではなく、ループ内で見つけること
ここに画像の説明を挿入します
できるようにすることです。簡単に実装できます。それ
ここに画像の説明を挿入します
は私たちが始めときです。???????????????????????????????????????????? ?????????????????????????????????????????????????? ?????????????????????????????????????????????????? ?????????????????????????????????????????????????? ?????????????????????????????????????????????????? ?????????????????????????????????????????????????? ?????????????????????????????????????????????????? ?????????????????????????????????????????????????? ?????????????????????????????????????????????????? ???????????????????????ループするとき、古いプレドリンクに含まれているキー値を直接取り出し、KEYMAP [を介してこの値を取得できます。 new pre-miniature]トラバースなし。未定義の場合は見つからないことを意味します。未定義の場合は見つかったことを意味します。このとき、oldChからノードを取り出す必要があります。次に、ノードを新しいノード(PatchVnode処理)に更新し、この項目をundefinedに設定してから、見つかったノードを古いノードの前に移動します。それでおしまい。
ここに画像の説明を挿入します
不必要な判断避けるために、ループの前に未定義をスキップする必要があるだけです
これで、forループを追加して見つかった最初の4つのケースと、forループが見つからなかった場合のみを処理しました。

ここに画像の説明を挿入します
これは簡単です。古いものの前に作成したポインタを追加するだけです。新しく作成されたため、古いノードにはこれがないため、ノードを処理するためにpatchVnodeは必要なく、新しいノードを作成するだけです。ここに画像の説明を挿入します
見つけたかどうかに関係なく、ポインタが下に移動することを忘れないでください。ここには間違いがありここに画像の説明を挿入します
、CreatElementはDOM要素ではなくノード全体を受け入れるため、.elmを追加する必要はありません。

次に、それはここに画像の説明を挿入します
完全に機能します。
単純なバージョンを作成したため、その属性は扱いませんでした。必要に応じて、createElementでデータを直接トラバースし、setAttrbuteを使用して属性値を設定します。

総括する

洗練された比較の実現は、4つのポインターとforループ(実際にはキーマップに置き換えられ、パフォーマンスがより最適化されます)を介して行われ、合計6つの状況があります。
つまり、新しい前に古い前に、古い後と新しい後に、これらの2つのトラバーサルに到達すると、patchVnode(old、new)のように、新しいノードと古いノードのデータが直接処理され、新しい
データは次のようになります。古いものに更新すると、ポインタが移動するはずです。
次に、古い前の新しい前、古い後の古い前の新しいがあります。これら2つが一致する場合、patchVnode(old、new)に加えて、古い前の新しい前の場合、新しいを挿入する必要があります。古い後のノードの前、古い前の新しい後者は、古いフロントの前に新しいフロントノードを挿入することです。それらは常に新しいタイプの前に挿入され、次に3番目のタイプが古いタイプの後に挿入され、4番目のタイプが古いタイプの前に挿入されることを忘れないでください。次に、ポインタをもう一度移動します。
その後、4つのうちどれも見つかりませんでした。KeyMAPクエリを開始します。
見つからない場合は、createElementを使用して直接新しいノードを作成し、古いノードの前に挿入するのは簡単です。
それを見つけた場合は、少し面倒です。見つけた古いノードを取り出し、keyMap [key]を介してこのノードを取得し、データ、patchVnodeを更新してから、古いノードの位置を未定義に設定します。これは、すでに移動されており、ついに旧フロントの真正面に挿入されました。
見つけられるかどうかにかかわらず、古いフロントの前面を挿入してから、新しいフロントを下に移動し続けます。

もう1つは、patchVnodeとupDateChildrenの巧妙さであり、相互に呼び出します。
patchVnodeは、新しいノードのテキスト/子を古いノードのテキスト/子に更新するなどのデータを更新するために使用され、古いノードと新しいノードに子がある場合、それらの子にノードがあることを証明します。このとき、upDateChildrenを呼び出す必要があります。呼び出し後、子は内部で判断され、patchVnodeを使用してデータを更新し、相互に呼び出します。相互呼び出し停止の鍵は、子の子ノードの子が空の場合、つまり子がない場合、patchVnodeはupDateChidlrenの呼び出しを停止しますが、ノードを直接更新することです。

おすすめ

転載: blog.csdn.net/lin_fightin/article/details/114648971
おすすめ