Vueソースコード解析における仮想DOMの詳しい説明

Vueソースコード解析における仮想DOMの詳しい説明

なぜ仮想DOMが必要なのでしょうか?

仮想 DOM はブラウザのパフォーマンス問題を解決するために設計されています。たとえば、1 回の操作で 10 回の DOM 更新があった場合、仮想 DOM はすぐに DOM を操作するのではなく、これらの 10 回の更新の差分コンテンツをローカル JS オブジェクトに保存し、最後にこの JS オブジェクトを一度に DOM ツリーにアタッチしてから後続の操作を実行し、多くの不要な計算を回避します。簡単に言えば、仮想 DOM は単純な JS オブジェクトとして理解でき、タグ名 (tag)、属性 (attrs)、子要素オブジェクト (children) の少なくとも 3 つの属性が含まれます。

  • ----- 要素ノード: 要素ノードは、私たちが普段目にする実際の DOM ノードに近いものです。ノードのラベル名詞を記述する tag 属性、クラスや属性などのノードのプロパティを記述する data 属性、含まれる子ノード情報を記述する children 属性があります。要素ノードに含まれる状況は比較的複雑なため、最初の 3 種類のノードのようにソース コードに直接書き込まれることはありません。
  • VNode の役割: 実際の DOM を操作することで消費されるパフォーマンスと引き換えに、js の計算パフォーマンスを使用します。
  • ----- Vue の仮想 DOM プロセス全体において、VNode はどのような役割を果たしますか? 実は、VNode の役割は非常に重要です。ビューをレンダリングする前に、記述したテンプレートを VNode にコンパイルしてキャッシュします。データの変更によりページを再レンダリングする必要がある場合は、データの変更後に生成された VNode と前回キャッシュされた VNode を比較して違いを確認します。そして、異なる VNode に対応する実際の DOM ノードが再レンダリングする必要があるノードとなり、最後に差分で作成された DOM ノードがビューに挿入され、最終的にビューの更新が完了します。データの変更前後の実際のDOMに対応する仮想DOMノードを生成することです。

仮想 DOM が必要な理由は何ですか?

----- JS の計算パフォーマンスを、実際の DOM の操作によって消費されるパフォーマンスと交換することです。Vue は、VNode クラスを通じてさまざまな種類の仮想 DOM ノードをインスタンス化し、さまざまな種類のノードによって生成される属性の違いを学習します。いわゆるさまざまな種類のノードは本質的に同じであり、すべて VNode クラスのインスタンスですが、インスタンス化中に渡されるパラメーターが異なります。
データ変更前後の VNode を使用して、後続の DOM-Diff を実行して差異を見つけ、最後に差異のあるビューのみを更新することで、実際の DOM をできるだけ操作せずにパフォーマンスを節約するという目的を達成できます。

----- そして、更新が異なる DOM ノードを見つけることで、実際の DOM に対する最小限の操作でビューを更新するという目的が達成されました。古い VNode と新しい VNode を比較して違いを見つけるプロセスは、いわゆる DOM-Diff プロセスです。DOM-Diff アルゴリズムは、仮想 DOM 全体の中核です。

パッチ

Vue では、DOM-Diff プロセスはパッチ プロセスと呼ばれます。パッチはパッチを意味し、アイデアを意味します。いわゆる古い VNode (odlNode) は、データが変更される前の対応する仮想 DOM ノードであり、新しい NVode は、データが変更された後にレンダリングされるビューに対応する仮想 DOM ノードです。したがって、生成された新しい VNode をベンチマークとして使用して、古い oldVNode と比較する必要があります。新しい VNode にノードがあり、古い oldVNode にない場合は、そのノードを古い oldVNode に追加します。新しい VNode になく、古い oldVNode にあるノードがある場合は、そのノードを古い oldVNode から削除します。古い VNode ノードと新しい VNode ノードの両方が存在する場合、新しい VNode が参照として使用され、古い oldVNode が更新されて、新しい VNode ノードと古い VNode ノードが同じになります。

パッチ全体はノードの作成に関するものです。新しい VNode は存在しますが、古いものはありません。古いoldVNodeに作成する

ノードの削除: ノードが新しい VNode に存在せず、古い VNode に存在する場合は、古い VNode から削除します。

ノードの更新: 新しいノードと古いノードの両方が利用可能な場合は、新しい VNode が参照として使用され、古い oldVNode が更新されます。

子ノードを更新する

/* 
    2 つの子ノード配列を比較するには、外側のループ newChildren と内側のループ oldCHildren 配列をループする必要があります。外側の newChildren 配列の各子ノードについて、内側の oldChildren 配列を調べて、同一の子ノードがあるかどうかを確認します*/
(i = 0 とします; i < newChildred.length; i++) {
    定数 newChild = newChildren[i]
    (j = 0; j < oldChildren.length; j++) の場合 {
        定数 oldChild = oldChildren[i]
        (新しい子 === 古い子) の場合 {
            // ...
        }
    }
}

上記のプロセスでは、次の4つの状況が発生します。

  1. 子ノードを作成します。newChildren の子ノードが oldChildren で同じ子ノードを見つけられない場合は、newChildren の子ノードが以前に存在せず、今回追加する必要があることを意味するため、子ノードを作成します。
  2. 子ノードを削除します。newChildren 内のすべての子ノードをループした後も、oldChildren に未処理の子ノードが残っている場合は、未処理の子ノードを破棄する必要があることを意味するため、これらのノードを削除します。
  3. 子ノードを移動します。newChildren の子ノードが oldChildren の同じ子ノードを見つけたが、位置が異なる場合は、子ノードの位置を調整する必要があることを意味します。次に、newChildren の子ノード 1 の位置に基づいて、oldChildren のノードの位置を調整し、newChildren と同じにします。
  4. ノードの更新: newChildren の子ノードが oldChildren の同じ子ノードを見つけ、位置が同じである場合、oldChildren のノードを更新して newChildren のノードと同じにします。

ノードの更新は新しい Vnode に基づいて行い、古い oldVnode が新しい VNode と同じになるように古い oldVnode を操作する必要があることを繰り返し強調してきました。

アップデートは 3 つの部分に分かれています。

VNodeとoldVNodeが両方とも静的ノードである場合、

前述したように、静的ノードはデータの変更とは関係がないため、すべてが静的ノードである場合は処理されずに直接スキップされます。

VNodeがテキストノードの場合

VNode がテキスト ノードである場合、つまりこのノードにプレーン テキストのみが含まれている場合は、oldVNode もテキスト ノードであるかどうかを確認するだけで済みます。テキスト ノードである場合は、2 つのテキストが異なるかどうかを比較します。テキストが異なる場合は、oldVNode のテキストを VNode のテキストと同じになるように変更します。oldVNode がテキスト ノードでない場合は、それが何であっても、setTextNode メソッドを直接呼び出してテキスト ノードに変更します。テキスト コンテンツは VNode と同じになります。

VNodeが要素ノードである場合、さらに次の2つのケースに分けられます。

  1. ノードには子ノードが含まれているため、この時点で古いノードに子ノードが含まれているかどうかを確認する必要があります。古いノードに子ノードが含まれている場合は、子ノードを再帰的に比較して更新する必要があります。
  2. 古いノードに子ノードが含まれていない場合、古いノードは空のノードまたはテキスト ノードである可能性があります。
  3. 古いノードが空のノードである場合は、新しいノードに子ノードのコピーを作成し、それを古いノードに挿入します。
  4. 古いノードがテキスト ノードの場合は、テキストをクリアし、新しいノードに子ノードのコピーを作成し、それを古いノードに挿入します。
  5. ノードには子ノードが含まれていません。ノードに子ノードが含まれず、テキスト ノードでもない場合は、ノードが空のノードであることを意味します。これは簡単に対処できます。以前のノードに何が含まれていたかに関係なく、それをクリアするだけです。
// ノード更新関数 patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    // vnode と oldVnode はまったく同じですか? そうであれば、プログラムを終了します if (oldVnode === vnode) {
        戻る
    }
    const elm = vnode.elm = 古いVnode.elm
    //vnode と oldVnode は両方とも静的ノードですか? はいの場合はプログラムを終了します if (isTrue(vnode.isStatic) && isTrue(vnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {
        戻る
    }
    定数 oldCh = oldVnode.children
    定数ch = vnode.children
    // vnode にテキスト属性がある場合、そうでない場合は if (isUndef(vnode.text)) {
        isDef(oldCh) と isDef(ch) のどちらが正しいか
            // 両方存在する場合は、子ノードが同じかどうかを判断します。異なる場合は、子ノードを更新します。if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
        }
        // vnodeの子ノードのみが存在する場合 else if (isDef(ch)) {
            /**
             * oldVnode にテキストがあるかどうかを判断します。 * ない場合は、Vnode の子ノードを実際の DOM に追加します。 * 場合は、DOM 内のテキストをクリアし、vnode の子ノードを実際の DOM に追加します。 * */
            if (isDef(oldVnode.text)) nodeOps.setTextContext(elm, '')
            addVnodes(elm, null, ch, 0, ch.length - 1, 挿入されたVnodeQueue)
        }
        // oldnodeの子ノードのみが存在する場合 else if (isDef(oldCh)) {
            // DOM 内のすべての子ノードをクリアします。removeVnodes(elm, oldCh, 0, oldCh.length - 1)
        }
        // vnode と oldnode に子ノードがないが、oldnode にテキストがある場合 else if (isDef(oldVnode.text)) {
            nodeOps.setTextContext(elm, '')
        }
        // 上記の 2 つの判断は 1 つの文にまとめることができます。vnode にテキストも子ノードもない場合は、対応する oldnode にあるものをすべてクリアします。} else if (oldVnode.text !== vnode.text) {
        nodeOps.setTextContext(elm, vnode.text)
    }
}

上記では、DOM-DIFF アルゴリズムである Vue のパッチについて学習しました。また、パッチ プロセスでは基本的に、ノードの作成、ノードの削除、ノードの更新という 3 つの処理が行われることがわかりました。 ノードの作成と削除は比較的簡単ですが、ノードの更新にはさまざまな状況に対応する必要があるため、より複雑なロジックが必要です。 更新プロセス中、9 つの Vnode すべてに子ノードが含まれる場合があります。子ノードを比較して更新するための追加ロジックがいくつかあります。そこで、この記事では、Vue で子ノードを比較する方法を学習します。

子ノードを更新する

新しい Vnode と古い Vnode が両方とも要素ノードであり、子ノードを含む場合、2 つのノード VNode インスタンスの chidlren 属性は、含まれている子ノード配列です。ループを介して 2 つの子ノードを比較します。外側のループは newChildren 配列、内側のループは oldChildren 配列です。各ループは外側の newChildren 配列の子ノードに対して実行され、内側の oldChildren 配列に移動して同じ子ノードがあるかどうかを確認します。

子ノードを作成する

子ノードが作成される位置は、すべての処理済みノードの後ではなく、すべての未処理ノードの前である必要があります。 処理されたノードの後に​​子ノードが挿入された場合、後で新しいノードを挿入すると、新しく追加された子ノードが台無しになってしまうからです。

子ノードを移動する

処理されていないすべてのノードが、移動する場所です。

子ノードの更新を最適化します。

先ほど、新しい VNode と古い VNode が両方とも要素ノードであり、両方とも子ノードを含む場合、Vue は最初に外側のレイヤーで newChildren 配列をループし、次に内側のレイヤーで oldChildren 配列をループすることを紹介しました。外側の newChildren 配列で子ノードをループするたびに、内側の oldChildren 配列を調べて同一の子ノードがあるかどうかを確認し、最終的にさまざまな状況に応じてさまざまな操作を実行します。まだ最適化できる領域はあります。たとえば、子ノードの数が多い場合、ループ アルゴリズムの時間計算量は非常に大きくなり、パフォーマンスの向上にはつながりません。

方法:

  1. まず、newChildren 配列内のすべての未処理の子ノードの最初の子ノードと、oldChildren 配列内のすべての未処理の子ノードの最初の子ノードを比較します。同じ場合は、ノード更新操作に直接進みます。
  2. 異なる場合は、newChildren 配列内のすべての未処理の子ノードの最後のノードと、oldChildren 配列内のすべての未処理の子ノードの最後の子ノードを比較します。同じ場合は、ノード更新操作に直接入ります。
  3. 異なる場合は、newChildren配列内のすべての未処理の子ノードの最後の子ノードとoldChildren配列内のすべての未処理の子ノードの最初の子ノードを比較します。同じ場合は、ノード更新操作に直接入ります。更新後、oldChildren配列のノードをnewChildren配列のノードと同じ位置に移動します。異なる場合は、
  4. 次に、newChildren 配列内のすべての未処理の子ノードの最初の子ノードと、oldChildren 配列内のすべての未処理の子ノードの最後の子ノードを比較します。同じ場合は、ノード更新操作に直接進みます。更新後、oldChildren 配列のノードを newChildren 配列のノードと同じ位置に移動します。
  5. 最後の 4 つのケースを試してもまだ異なる場合は、以前と同じ方法でノードを検索します。
    大量のデータと二重ループによる時間の複雑さの増加によって引き起こされるパフォーマンスの問題を回避するために、Vue は子ノード配列内の 4 つの特別な位置を比較することを選択します。つまり、新しい前面と古い前面、新しい背面と古い背面、新しい背面と古い前面、新しい前面と古い背面です。

前回の記事では、Vue における仮想 DOM と仮想 DOM のパッチ (DOM-Diff) 処理について紹介しました。仮想 DOM が存在するための必要条件は VNode が存在することですが、では VNode はどこから来るのでしょうか。 ユーザーが書いたテンプレートをコンパイルするとVNodeが生成される

テンプレートのコンパイル:

テンプレートコンパイルとは:ユーザーのテンプレートタグに記述されたネイティブ HTML に類似したコンテンツをコンパイルし、ネイティブ HTML のコンテンツを検索し、非ネイティブ HTML を検索します。一連の論理処理の後、レンダリング関数が生成されます。このレンダリング関数のプロセスをテンプレートコンパイルプロセスと呼びます。レンダリング関数はテンプレートコンテンツからVNodeを生成する

全体的なレンダリング処理、いわゆるレンダリング処理は、一連の処理を経て、ユーザーが作成したネイティブ HTML に似たテンプレートをビューに反映させるというものです。この処理については、すでに上で説明しました。

抽象構文木 AST:

  • ユーザーがテンプレート タグに記述したテンプレートは、Vue の文字列の集まりです。では、この文字列の集まりを解析し、要素のタグ、属性、変数の補間、その他の有効な情報を抽出するにはどうすればよいでしょうか。これには、抽象構文木と呼ばれるものの助けが必要です。
    抽象構文木 (構文ツリーとも呼ばれる) は、ソース コードの構文構造の抽象表現です。プログラミング言語の構文構造をツリーの形式で表します。ツリー内の各ノードは、ソース コード内の構造を表します。構文が抽象的である理由は、ここでの構文が実際の構文に現れるすべての構造を表しているわけではないからです。たとえば、ネストされた括弧はツリー構造に暗黙的に含まれており、ノード i の形式では表示されません。

具体的なプロセス:

  • 一連の文字列テンプレートを抽象構文木 (AST) に解析した後、さまざまな操作を実行できます。処理後、AST を使用してレンダリング関数が生成されます。具体的な 3 つのプロセスは、次の 3 つの段階に分けられます。

テンプレート解析フェーズ: 正規表現を使用して、一連のテンプレート文字列を抽象構文木 AST に解析します。

最適化フェーズ: ASTをコンパイルし、静的ノードを見つけてマークする

コード生成フェーズ: ASTをレンダリング関数に変換する

テンプレートのコンパイルによってのみ、仮想 DOM とそれに続くビューの更新が可能になります。

要約する

Vue ソースコード解析と仮想 DOM に関するこの記事はこれで終わりです。Vue 仮想 DOM に関するより関連性の高いコンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • この記事は、Vueの仮想Domとdiffアルゴリズムを理解するのに役立ちます。
  • Vue仮想DOMの原理
  • Vue 仮想 DOM の問題について
  • Vue 仮想 DOM クイックスタート
  • Vueにおける仮想DOMの理解のまとめ
  • Vue 仮想 Dom から実際の Dom への変換
  • Vue の仮想 DOM に関する知識ポイントのまとめ

<<:  IIS7~IIS8.5 サーバープロトコルヘッダーの削除または変更

>>:  MySQL インフラストラクチャ チュートリアル: クエリ ステートメント実行プロセスの詳細な説明

推薦する

VUE と Canvas を使用して Thunder Fighter タイピング ゲームを実装する方法

今日は、サンダーファイタータイピングゲームを実装します。ゲームプレイは非常に簡単です。それぞれの「敵...

ReactプロジェクトにSCSSを導入する方法

まず依存関係をダウンロードします yarn sass-loader ノード sass を追加します次...

HTMLを圧縮しない理由はいくつかある

理由は簡単です。 HTML ドキュメントでは、複数の空白文字は 1 つの空白文字と同等です。つまり、...

mysql8.0.12 でルートパスワードをリセットする方法

データベースをインストールした後、誤ってインストール ウィンドウを閉じたり、長期間 root ユーザ...

Linuxのファイルとフォルダの権限を操作する方法

Linux のファイル権限まず、現在のディレクトリ内のファイルの内容を確認しましょう。 ls -l ...

Vue3 での Teleport の使用に関する詳細な説明

目次テレポートの目的テレポートの仕組みこの記事では、以下の内容を取り上げます。テレポートの目的テレポ...

MySQLフィールド定義でnullを使用しない理由の分析

NULL が頻繁に使用されるのはなぜですか? (1)Javaのnull Java の NullPoi...

Windows での MySQL 5.7.18 のインストールと設定のチュートリアル

この記事では、WindowsでのMySQL 5.7.18のインストールと設定のチュートリアルを参考ま...

JavaScript で実装された 7 つのソート アルゴリズムの概要 (推奨!)

目次序文バブルソート基本アルゴリズム2 番目の書き方は、基本的なアルゴリズムに基づいて改良されていま...

Docker nginx + https サブドメイン設定の詳細なチュートリアル

今日はたまたま友人のサーバーの移転を手伝うことになり、サーバーの基本的な設備の設定を行ったのですが、...

mysql 変数の使用例の分析 [システム変数、ユーザー変数]

この記事では、例を使用して MySQL 変数の使用方法を説明します。ご参考までに、詳細は以下の通りで...

MySQL データベースの大文字と小文字の区別の問題

MySQL では、データベースはデータ ディレクトリ内のディレクトリに対応します。データベース内の各...

ウェブページを白黒に変換します(Google、Firefox、IE、その他のブラウザと互換性があります)

CSSファイルに書き込むコードをコピーコードは次のとおりです。 01.html {グレイスケール(1...

vue で h5 側のアプリを開きます (Android か Apple かを判断します)

1. 開発環境 vue+vant 2. コンピュータシステム Windows 10 Profess...

JavaScript が重複したネットワークリクエストを防ぐ方法の例

序文開発中は、インターフェース要求の繰り返しによってさまざまな問題が発生することがよくあります。ネッ...