Vue2.x の仮想 DOM 差分原理の詳細な理解

Vue2.x の仮想 DOM 差分原理の詳細な理解

1. はじめに

Vue の核となるのは双方向バインディングと仮想 DOM (以下、vdom と呼びます) です。双方向バインディングについては、Xylophone の記事「Vue の原則の分析と双方向バインディング MVVM の実装」を参照してください。 Vdom はツリー構造であり、そのノードは vnode です。 Vnode はブラウザ DOM 内のノードと 1 対 1 で対応します。対応するノードには、vnode の elm 属性を通じてアクセスできます。

vdom は純粋な JS オブジェクトであるため、操作は非常に効率的ですが、vdom への変更は最終的に DOM 操作に変換されます。効率的な DOM 操作を実現するには、効率的な仮想 DOM 差分アルゴリズムが必要です。

Vue の diff アルゴリズムは snabbdom に基づいており、興味のある友人はそれをチェックすることを選択できます。

これは「React の diff アルゴリズム」の典型的な図です。 Vue の diff アルゴリズムは同じです。つまり、同じレベルの vnode 間でのみ diff が実行され、同じレベルの vnode 間で diff が再帰的に実行され、最終的に DOM ツリー全体が更新されます。同じレベルの vnode diff の詳細は何ですか?まさにこの記事ではこれについて説明します。

2. 例

この簡略化された例を使用して、以下の diff プロセスを説明します。

上記の例のように、更新前はノードリストが 1 から 10 まで並んでおり、更新後はノードリストがランダムな順序で並んでいます。図に次のタイプのノード変更をリストします。

(1)頭と尾が同じノード:1、10など

(2)同じ先頭と末尾を持つノード:2と9など(同じ先頭と末尾を持つノードの処理後)

(3)新たに追加されたノード:11

(4)削除されたノード:8

(5)その他のノード:3、4、5、6、7

3. シンプルな差分

単純な diff アルゴリズムは次のように設計できます。

newVdom のノードを 1 つずつ走査し、oldVdom 内での位置を見つけます。見つかった場合は、対応する DOM 要素を移動します。見つからない場合は、新しいノードが追加されたことを意味するため、新しいノードが作成され、挿入されます。トラバーサルが完了した後、oldVdom にまだ処理されていないノードがある場合は、これらのノードが newVdom で削除されていることを意味するため、削除するだけです。

よく考えてみると、ほぼすべてのステップで DOM を移動する必要があり、全体的な DOM 構造があまり変わらない場合はコストが非常に高くなります。実際のところ、DOM があまり変化しないということはよくあります。多くの場合、特定のノードのテキストを変更するだけで済みます。

次に、Vueのdiff実装を見てみましょう。

4. Vueのdiff実装

上記の例では、oldStart+oldEnd と newStart+newEnd という 2 つのポインターのペアを描画しました。これらはそれぞれ、oldVdom と newVdom の開始点と終了点に対応します。開始点と終了点の前のノードが処理対象となるノードです。 Vue は vnode の処理を​​継続し、開始点と終了点のペアが出会うまでポインターを移動します。 Vue は、処理されたノードを oldVdom と newVdom の両方で処理済みとしてマークします (マーク方法については後で説明します)。 Vue は以下の対策を講じることで diff のパフォーマンスを向上させます。

1. 特別なシナリオを優先する

(1)先頭に同じ種類のノード、末尾に同じ種類のノード

これらのノードの位置は更新の前後で変化しないため、対応するDOMを移動する必要はありません。

(2)頭と尾/尾と頭に同じ種類のノードがある

このタイプのノードの場所は非常に明確なので、探すのに時間を費やす必要はなく、DOMを直接移動するだけです。

これらのシナリオを処理すると、一方では、移動する必要のない一部の DOM が迅速に処理され、他方では、処理されるノードの数が削減されるため、後続の操作の処理範囲が縮小され、パフォーマンスが向上します。

(II)「その場での再利用」

「インプレース再利用」とは、Vue が DOM を可能な限り再利用し、DOM の移動を可能な限り避けることを意味します。 Vue は、更新前と更新後にポインターが同じノードを指しているかどうかを判断するときに、実際に同じ DOM ノードを参照する必要はありません。実際には、同じタイプのノードを指しているかどうかを判断するだけです (たとえば、2 つの異なる div は DOM 上では異なりますが、同じタイプのノードに属しています)。同じタイプのノードの場合、Vue は DOM を直接再利用します。これの利点は、DOM を移動する必要がないことです。上記の例を見ると、10 個のノードすべてが div である場合、diff プロセス全体で DOM 移動操作は行われません。

「インプレース再利用」については、Vue の公式ドキュメントに記載されています。それは利益をもたらしますが、いくつかの問題も引き起こします。友達がレビューできます。

https://cn.vuejs.org/v2/guide/list.html#key

再利用可能な要素を管理するには、https://cn.vuejs.org/v2/guide/conditional.html#Use-key- を使用します。

5. ステップバイステップの解剖例

1. 全体図

まずは全体像を見てみましょう。全体の diff は 2 つの部分に分かれています。

(1)最初の部分はループです。ループ内には分岐ロジックがあります。各ループは 1 つのブランチにのみ入ります。各ループはノードを処理します。処理後、ノードは処理済みとしてマークされます (oldVdom と newVdom の両方をマークする必要があります。ノードがいずれかの vdom にのみ表示される場合は、他の vdom でマークする必要はありません)。マークする方法は2つあります。ノードが vdom のポインターに正確に位置している場合は、ポインターを移動して未処理リストから除外します。それ以外の場合は、他の方法を使用する必要があります。 Vue のアプローチは、ノードを undefined に設定することです。

(2)ループ終了後、newVdomまたはoldVdomに未処理のノードが残る場合がある。 newVdom に未処理のノードがある場合、これらのノードは新しく追加されたノードであり、処理されます。 oldVdom にそのようなノードがある場合、それらは削除する必要があるノードであり、それに応じて DOM ツリーで削除されます。

全体のプロセスは、更新前と更新後の vdom の違いを徐々に見つけ、その違いを DOM ツリー (つまり、パッチ) に反映することです。特に重要なのは、Vue のパッチはリアルタイムであり、DOM を操作するためにすべての変更をまとめてパッケージ化するわけではないということです (React は更新をキューに入れて集中的に処理します)。友達は、これはパフォーマンスが悪いのかと聞いてくるでしょう。実際、最新のブラウザではこのような DOM 操作が最適化されており、違いはありません。

(II)段階的な分析

(1)先頭の同じタイプのノードを処理する。つまり、oldStartとnewStartは同じタイプのノードを指す。例えば、次の図のノード1である。

この場合、ノード 1 の変更が DOM に更新され、マークされます。マーキング方法は、oldStart と newStart を 1 つ後ろにシフトすることです。プロセス中に DOM を移動する必要はありません (属性の変更、テキスト コンテンツの変更など、DOM の更新が必要になる場合があります)。

(2)末尾の同じタイプのノードを処理する。つまり、oldEndとnewEndが同じタイプのノードを指している場合(次の図のノード10など)

この場合もケース(1)と同様に、ノード10の変更がDOMに更新され、oldEndとnewEndが1つ前へ移動してマークされます。 DOM を移動する必要もありません。

(3)先頭と末尾/末尾の先頭、つまりoldStartとnewEndで同じタイプのノードを処理する場合、およびoldEndとnewStartが同じタイプのノードを指している場合(下図のノード2と9など)

まず、ノード 2 を見てみましょう。これは実際には後方に移動します。どこに移動するのでしょうか? oldEnd が指すノード (つまり、ノード 9) に移動します。移動後、ノードをマークし、oldStart を 1 つ後ろに移動し、newEnd を 1 つ前に移動します。

手術が完了すると、状況は次のようになります

同様にノード9も同じように処理され、処理後は次のようになる。

(4)新しく追加されたノードの処理

newStartはノード11の位置に到達し、ノード11はoldVdomに見つからないため、新しく追加されたことがわかります。

次に、新しいノードを作成し、それを DOM ツリーに挿入します。どこに挿入しますか?これを oldStart が指すノード (つまり、ノード 3) の前に挿入し、newStart を 1 つ戻して処理済みとしてマークします (oldVdom にはノード 11 がないため、マーク処理中にそのポインターを移動する必要はありません)。処理後は以下のようになります

(5)更新されたノードの処理

ステップ(4)の後、newStartはoldVdom内で見つかるノード7に到達しますが、これはポインター位置にはなく(oldVdom内のoldStartからoldEndまでの区間のノードを検索)、その位置が移動したことを示しています。

次に、それを DOM ツリー内で移動する必要があります。どこに移動すればいいでしょうか?それを oldStart が指すノード (つまりノード 3) に移動します。同時に、ノードを処理済みとしてマークします。これはこれまでのケースとは少し異なります。 newVdom では、ノードはポインターの下にあるため、newStart を移動してマークすることができます。 oldVdomでは、ノードはポインタにないので、それをundefinedに設定してマークする方法を使用します(マークする必要があるかどうかは後で説明します)

処理後は以下のようになる

(6)処理ノード3、4、5、6

ステップ(5)の処理後、満足のいくシーンが表示されます。 NewStart と oldStart は再び同じノードを指します (つまり、両方ともノード 3 を指します)。とても簡単です。 (1)の方法でポインタを移動させるだけです。とても効率的です。 3、4、5、6も同様に処理されます。処理後は以下のようになります

(7)削除する処理ノード

最初の 6 つのステップ (実際には最初の 6 つのステップはループで実行されます) の後、newStart が newEnd を越えて、それらが出会うことがわかります。この時点では、oldStart と oldEnd は一致していません。つまり、これら 2 つのポインター間のノード (それらが指すノード、つまり上図のノード 7 と 8 を含む) が、この更新で削除されるノードです。

OK、それではDOMツリーからそれらを削除します。前のセクションに戻って、ノード 7 をマークしましょう。マークが必要なのはなぜでしょうか?マークの目的は、それが処理されたこと、そしてそれが新しい DOM に表示される必要があるノードであることを Vue に伝えることです。削除しないでください。ここではノード 8 のみを削除する必要があります。

アプリケーションでは、oldVdom の開始点と終了点が一致するが、newVdom の開始点と終了点が一致しない状況が発生することもあります。このとき、newVdom 内の未処理のノードを処理する必要があります。これらのノードは更新中に追加されたノードであり、DOM ツリーに挿入する必要があります。

この時点で、diffプロセス全体は終了です。

Vue の diff アルゴリズムは、動的プログラミング アルゴリズムの「a から b への最小編集距離を計算する」という典型的なケースに多少似ていますが、実際にはまったく異なります。 Vue の diff は比較的軽量です。興味のある方は、関連情報を参照して詳細をご確認ください。

オリジナルリンク: https://www.qcloud.com/community/article/648055

著者: 王玉林

[この記事は、51CTOコラムニスト「Tencent Cloud Technology Community」によるオリジナル記事です。転載の許可を得るには、51CTOを通じて原著者に連絡してください。

この著者の他の記事を読むにはここをクリックしてください

<<:  IaaS とは何ですか?最新のデータセンタープラットフォーム

>>:  Rancher Labs「Rancher 2.0リリースと中国ユーザーおよびパートナーカンファレンス」が盛況のうちに開催されました

推薦する

Xiaomi ルーターが 404 ページを「ハイジャック」される危険にさらされる

最近、一部のメディアは、Xiaomiルーターが使用中にHTTP403および404ページをハイジャック...

imidc 香港データセンターの BGP ネットワークの独立サーバーの簡単な評価 (30M 帯域幅)

現在、imidcでは香港サーバー(国際BGP)、日本サーバー(cn2+BGP)、台湾サーバー(BGP...

ウェブサイトを構築するにはどれくらいの費用がかかりますか?

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

#11.11# 恒創科技:香港クラウドサーバー(OpenStack)40%オフ、香港300G高防御サーバー40%~60%オフ

恒創科技の11.11(独身の日)プロモーションが始まりました:(1)香港クラウドサーバー、CN2+B...

ウェブサイトのヒートマップを見るときは、検索されたキーワードと組み合わせて本質を見る必要があります。

ヒートマップが統計ツールに追加されて以来、ほぼ毎日見ています。ヒートマップはユーザーのニーズを調査し...

チーム向けクラウド コンピューティング サービスの比較

これで、チームをクラウドに移行する準備が整いました。顧客とチームはクラウド内に存在します。携帯電話、...

百度製品の外部リンクを放棄してもまだ明るい未来はある

ウェブサイトの外部リンクを最適化することも最適化の一部です。外部リンクのサポートがなければ、ウェブサ...

新しいウェブサイトに外部リンクを構築する方法

初心者は、新しいウェブサイトを手に入れても、それをどう操作すればいいのかわかりません。私もかつてはそ...

SEM は中年の危機に直面しているのでしょうか?

検索マーケティングや広告キャンペーンの重要なストーリーを探すとき、インサイトが果たす役割に驚かされる...

ホームページ最適化の詳細な手順

これまで、ホームページの質の悪さが原因でユーザーが離脱してしまったウェブサイトは数え切れないほどあり...

中国モバイルゲーム業界動向レポート

レポートキーワードモバイルインターネットモバイルゲームH5 ミニゲーム 機能ゲーム 独立系ゲーム 海...

ユーザーエクスペリエンス分析:Weiboコンポーネントの改訂

前面に書かれたWeibo コンポーネントは、サードパーティのアクセス ユーザーが開発を必要とせずに ...

推奨される実践的なWeiboマーケティング最適化テクニック

ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービスまず、Weiboマーケテ...

BuyVM の VPS を 1Gbps 帯域幅から 10Gbps 無制限トラフィックにアップグレードするにはどうすればよいでしょうか?

誰もが知っていることですが、buyvm の VPS のデフォルトの帯域幅は 1Gbps で、トラフィ...