この記事では、Vue の仮想 dom 比較原理について説明します (例を示して説明します)。必要な方は参考にしていただければ幸いです。
まず、なぜ仮想 dom 比較ステージがあるのかについて話しましょう。Vue がデータ駆動型ビューであることはわかっていますが (データの変更によりビューも変更されます)、特定のデータが変更されるとそのことがわかります。 、ビューは部分的です。データに対応するビューを正確に見つけて、全体を再レンダリングするのではなく更新することで更新するにはどうすればよいですか?次に、データ変更前後の DOM 構造を取得し、相違点を見つけて更新する必要があります。
仮想 dom は本質的に、実際の dom から抽出された 単純なオブジェクトです。単純な p には 200 を超える属性が含まれていますが、実際に必要なのは tagName
だけである可能性があるのと同様に、実際の dom に対する直接操作はパフォーマンスに大きく影響します。
簡略化された仮想ノード (vnode) には、大まかに次の属性が含まれます:
{ tag: 'p', // 标签名 data: {}, // 属性数据,包括class、style、event、props、attrs等 children: [], // 子节点数组,也是vnode结构 text: undefined, // 文本 elm: undefined, // 真实dom key: undefined // 节点标识 }
仮想 dom の比較は、新しいノードを見つけることです。 (vnode ) と古いノード (oldVnode) を比較し、相違点にパッチを当てます。一般的なプロセスは次のとおりです
#古いノードと新しいノードが類似していない場合は、新しいノードに基づいて DOM を直接作成します。が類似している場合は、まずクラス、スタイル、イベント、プロパティ、属性などのデータを比較し、相違がある場合は、対応する更新関数を呼び出してから、子ノードの比較に を使用します。 diff アルゴリズム 、これがこの記事の焦点であり、難しさです。
Children Compare プロセス中に、同様の
childVnode が見つかった場合、
再帰的にパッチ適用ウィンドウが表示されることに注意してください。プロセス。 ソースコード解析
今回はソースコード解析を簡潔に書きますが、あまり書きすぎると読む気がなくなります(┬_┬)
Start
まず
patch() 関数を見てみましょう: <pre class="brush:php;toolbar:false">function patch (oldVnode, vnode) {
var elm, parent;
if (sameVnode(oldVnode, vnode)) {
// 相似就去打补丁(增删改)
patchVnode(oldVnode, vnode);
} else {
// 不相似就整个覆盖
elm = oldVnode.elm;
parent = api.parentNode(elm);
createElm(vnode);
if (parent !== null) {
api.insertBefore(parent, vnode.elm, api.nextSibling(elm));
removeVnodes(parent, [oldVnode], 0, 0);
}
}
return vnode.elm;
}</pre>
この関数は、古い vnode と新しい vnode の 2 つのパラメーターを受け取ります。渡される 2 つのパラメータには大きな違いがあります。違い: oldVnode の elm
は実際の dom を指しますが、vnode の elm
は未定義です...ただし、patch の後()
メソッド、vnode の elm
もこの (更新された) 実 dom を指します。 古い vnode と新しい vnode が類似しているかどうかを判断する
メソッドは非常に簡単で、tag
と key## かどうかを比較することです。 # 一貫性があります。
function sameVnode (a, b) { return a.key === b.key && a.tag === b.tag; }パッチ古い vnode と新しい vnode の間の
不一致に対する解決策は非常に簡単です。つまり、vnode に基づいて実際の dom を作成し、# を置き換えます。 ##elm
を oldVnode DOM ドキュメントに挿入します。新旧の vnode の一貫した処理については、前によく言及したパッチ適用です。パッチ当てとは具体的に何ですか? patchVnode()
メソッドを見てください:
function patchVnode (oldVnode, vnode) { // 新节点引用旧节点的dom let elm = vnode.elm = oldVnode.elm; const oldCh = oldVnode.children; const ch = vnode.children; // 调用update钩子 if (vnode.data) { updateAttrs(oldVnode, vnode); updateClass(oldVnode, vnode); updateEventListeners(oldVnode, vnode); updateProps(oldVnode, vnode); updateStyle(oldVnode, vnode); } // 判断是否为文本节点 if (vnode.text == undefined) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) } else if (isDef(ch)) { if (isDef(oldVnode.text)) api.setTextContent(elm, '') addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { api.setTextContent(elm, '') } } else if (oldVnode.text !== vnode.text) { api.setTextContent(elm, vnode.text) } }
パッチは実際にさまざまな updateXXX() 関数を呼び出して、実際の dom のさまざまな属性を更新しています。各更新関数は似ています。例として updateAttrs()
を取り上げます。
function updateAttrs (oldVnode, vnode) { let key, cur, old const elm = vnode.elm const oldAttrs = oldVnode.data.attrs || {} const attrs = vnode.data.attrs || {} // 更新/添加属性 for (key in attrs) { cur = attrs[key] old = oldAttrs[key] if (old !== cur) { if (booleanAttrsDict[key] && cur == null) { elm.removeAttribute(key) } else { elm.setAttribute(key, cur) } } } // 删除新节点不存在的属性 for (key in oldAttrs) { if (!(key in attrs)) { elm.removeAttribute(key) } } }
属性 (Attribute
) の更新関数の一般的な考え方は次のとおりです。
vnode 属性をトラバースします。oldVnode と異なる場合は、setAttribute()
を呼び出して変更します。 oldVnode 属性 (同じでない場合)
- を呼び出して、vnode 属性を削除します。
-
booleanAttrsDict[key]ブール属性辞書にあるかどうかを判断する
という判定があることがわかります。 -
['allowfullscreen'、'async'、'autofocus'、'autoplay'、'checked'、'compact'、'controls'、'declare'、...]
、自動再生をオフにしたい場合は、この属性を削除する必要があります。例:
すべてのデータを比較したら、子ノードを比較します。まず、現在の vnode がテキスト ノードであるかどうかを判断します。テキスト ノードの場合は、子ノードの比較を考慮する必要はありません。要素ノードの場合は、次の 3 つの状況で考慮する必要があります。 ## 古いノードと新しいノードの両方に子があり、子ノードの比較を入力します (差分アルゴリズム)。
新しいノードには子がありませんが、古いノードには子があるため、ループ内で dom ノードを削除します。##新しいノードには子がありますが、古いノードには子がありません。ノードに子がない場合は、ループ内で dom ノードを作成します。
- 後の 2 つの状況は、最初の状況である
子ノードの比較
を直接分析します。 - diff アルゴリズム サブノード比較のこの部分には多くのコードがあります。まず原理について説明してから、コードを投稿します。まず、子ノードを比較している図を見てください:
-
図の
oldCh
とnewCh
は、それぞれ古い子ノード配列と新しい子ノード配列を表し、独自の先頭ポインターと末尾ポインターoldStartIdx
、を持ちます。 oldEndIdx
、newStartIdx
、newEndIdx
、vnode は配列に格納されており、理解しやすいように、異なるものを表す a、b、c、d などに置き換えられます。タグのタイプ (p、span、p) vnode オブジェクト。子ノードの比較は、基本的に、先頭ノードと末尾ノードのループ比較です。ループの終了の兆候は、古い子ノード配列または新しい子ノード配列が走査されたことです (つまり、
oldStartIdx > oldEndIdx || newStartIdx > newEndIdx
)。 サイクル プロセス を大まかに見てみましょう:最初のステップ 相互比較 。それらが類似している場合、古いヘッド ポインターと新しいヘッド ポインターは後方に移動し (つまり、
oldStartIdx
&&newStartIdx
)、実際の DOM は変更されず、類似していない場合は次のサイクルに入ります。 、2番目のステップに入ります。2 番目のステップ 末尾同士を比較します。それらが類似している場合、古い終了ポインタと新しい終了ポインタは前方に移動され (つまり、
oldEndIdx--
&&newEndIdx--
)、実際の DOM は変更されず、次のサイクルに入ります。 ; 類似していない場合は、3 番目のステップに進みます。3 番目のステップ 頭と尾を比較します。それらが類似している場合、古いヘッド ポインタは後方に移動し、新しいテール ポインタは前方に移動します (つまり、
oldStartIdx
&&newEndIdx--
)。未確認の dom シーケンスのヘッドは、終了して次のサイクルに入ります。同様ではありません。ステップ 4 に進みます。ステップ 4 尻尾と頭を比較します。それらが類似している場合、古い末尾ポインタは前方に移動され、新しい先頭ポインタは後方に移動されます (つまり、
oldEndIdx--
&&newStartIdx
)。未確認の dom シーケンスの末尾が移動されます。同様ではなく、ステップ 5 に進みます。5 番目のステップは、ノードにキーがあり、同じ Vnode が古い子ノード配列で見つかった場合 (タグとキーの両方が一致している)、その dom を先頭に移動することです。現在の実際の dom シーケンスの新しいヘッド ポインターが後方に移動します (つまり、
newStartIdx
)。それ以外の場合は、vnode (vnode[newStartIdx].elm
) に対応する dom が挿入されます。現在の実際の dom シーケンスの先頭に移動し、新しい先頭ポインタは後方に移動します (つまり、newStartIdx
)。
まずはキーなしの状況を見て、よりわかりやすくアニメーションを付けてみましょう。
# この図を読めば、diff アルゴリズムの本質がよりよく理解できると思います。プロセス全体は比較的単純です。上の図では、各状況を含む合計 6 つのサイクルが入力されています。それらを 1 つずつ説明しましょう:
初回はすべて似ています (両方とも
です)。 a
)、dom は変化せず、新旧両方のヘッド ポインタが後方に移動します。a
ノードが確認された後の実際の dom シーケンスはa,b,c,d,e,f
、未確認の dom シーケンスはb,c,d, e,f
;2 回目は尾と尾が似ています (どちらも
f
)、dom は変化せず、古い尾と新しい尾ポインタが前に移動します。f
ノードが確認された後の実際の dom シーケンスはa,b,c,d,e,f
、未確認の dom シーケンスはb,c,d, e
;3 回目は、先頭と末尾が似ています (両方とも
b
)。現在残っている実際の dom シーケンスの先頭が に移動されます。最後に、古いヘッド ポインタが後方に移動し、新しいヘッド ポインタが後方に移動し、テール ポインタが前方に移動します。b
ノードが確認された後の実際の dom シーケンスはa,c,d,e,b,f
、未確認の dom シーケンスはc,d,e## になります。 #;
- 4 回目は、末尾と先頭が類似しています (両方とも
e
)。現在残っている実際の dom シーケンスの末尾が先頭に移動されます。 、古いテールポインタは前方に移動し、新しいヘッドポインタはシフトの後ろに移動します。
eノードが確認された後の実際の dom シーケンスは
a,e,c,d,b,fとなり、未確認の dom シーケンスは
c,dになります。
- 5 回目は同様ではなく、未確認の dom シーケンスの先頭に直接挿入されます。
g
ノードが挿入された後の実際の dom シーケンスは
a,e,g,c,d,b,f、未確認の dom シーケンスは
c,d## になります。 #; 6 回目は同様ではなく、未確認の dom シーケンスの先頭に直接挿入されます。 - h
ノードが挿入された後の実際の dom シーケンスは
a,e,g,h,c,d,b,f
となり、未確認の dom シーケンスはになります。 c,d
; ただし、ループ終了後は、次の 2 つの状況を考慮する必要があります。
- 新しいバイト ポイント配列 (newCh ) が通過されました (
- newStartIdx > newEndIdx
)。次に、冗長な古い dom (
newStartIdx -> newEndIdxoldStartIdx -> oldEndIdx
) をすべて削除する必要があります。上記の例では、c,d
; です。 ##new バイト ポイント配列 (oldCh) が走査されました (oldStartIdx > oldEndIdx
)。次に、追加の新しい dom ( ) をすべて追加する必要があります。
上面说了这么多都是没有key的情况,说添加了
:key
可以优化v-for
的性能,到底是怎么回事呢?因为v-for
大部分情况下生成的都是相同tag
的标签,如果没有key标识,那么相当于每次头头比较都能成功。你想想如果你往v-for
绑定的数组头部push数据,那么整个dom将全部刷新一遍(如果数组每项内容都不一样),那加了key
会有什么帮助呢?这边引用一张图:有
key
的情况,其实就是多了一步匹配查找的过程。也就是上面循环流程中的第五步,会尝试去旧子节点数组中找到与当前新子节点相似的节点,减少dom的操作!有兴趣的可以看看代码:
function updateChildren (parentElm, oldCh, newCh) { let oldStartIdx = 0 let newStartIdx = 0 let oldEndIdx = oldCh.length - 1 let oldStartVnode = oldCh[0] let oldEndVnode = oldCh[oldEndIdx] let newEndIdx = newCh.length - 1 let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx] let oldKeyToIdx, idxInOld, elmToMove, before while (oldStartIdx oldEndIdx) { before = isUndef(newCh[newEndIdx+1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) } }
以上がVueにおける仮想dom比較の原理の紹介(サンプル解説)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

JavaScriptは、現代のWeb開発のコア言語であり、その多様性と柔軟性に広く使用されています。 1)フロントエンド開発:DOM操作と最新のフレームワーク(React、Vue.JS、Angularなど)を通じて、動的なWebページとシングルページアプリケーションを構築します。 2)サーバー側の開発:node.jsは、非ブロッキングI/Oモデルを使用して、高い並行性とリアルタイムアプリケーションを処理します。 3)モバイルおよびデスクトップアプリケーション開発:クロスプラットフォーム開発は、反応および電子を通じて実現され、開発効率を向上させます。

JavaScriptの最新トレンドには、TypeScriptの台頭、最新のフレームワークとライブラリの人気、WebAssemblyの適用が含まれます。将来の見通しは、より強力なタイプシステム、サーバー側のJavaScriptの開発、人工知能と機械学習の拡大、およびIoTおよびEDGEコンピューティングの可能性をカバーしています。

JavaScriptは現代のWeb開発の基礎であり、その主な機能には、イベント駆動型のプログラミング、動的コンテンツ生成、非同期プログラミングが含まれます。 1)イベント駆動型プログラミングにより、Webページはユーザー操作に応じて動的に変更できます。 2)動的コンテンツ生成により、条件に応じてページコンテンツを調整できます。 3)非同期プログラミングにより、ユーザーインターフェイスがブロックされないようにします。 JavaScriptは、Webインタラクション、シングルページアプリケーション、サーバー側の開発で広く使用されており、ユーザーエクスペリエンスとクロスプラットフォーム開発の柔軟性を大幅に改善しています。

Pythonはデータサイエンスや機械学習により適していますが、JavaScriptはフロントエンドとフルスタックの開発により適しています。 1. Pythonは、簡潔な構文とリッチライブラリエコシステムで知られており、データ分析とWeb開発に適しています。 2。JavaScriptは、フロントエンド開発の中核です。 node.jsはサーバー側のプログラミングをサポートしており、フルスタック開発に適しています。

JavaScriptは、最新のブラウザにすでに組み込まれているため、インストールを必要としません。開始するには、テキストエディターとブラウザのみが必要です。 1)ブラウザ環境では、タグを介してHTMLファイルを埋め込んで実行します。 2)node.js環境では、node.jsをダウンロードしてインストールした後、コマンドラインを介してJavaScriptファイルを実行します。

Quartzタイマーを使用してタスクをスケジュールする場合、Quartzでタスク通知を事前に送信する方法、タスクの実行時間はCron式によって設定されます。今...

JavaScriptプログラミング、プロトタイプチェーンの関数パラメーターの理解と操作のJavaScriptのプロトタイプチェーンの関数のパラメーターを取得する方法は、一般的で重要なタスクです...

WeChatアプレットWeb-ViewでVue.jsを使用する動的スタイルの変位障害がvue.jsを使用している理由の分析...


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

AtomエディタMac版ダウンロード
最も人気のあるオープンソースエディター

ZendStudio 13.5.1 Mac
強力な PHP 統合開発環境

Dreamweaver Mac版
ビジュアル Web 開発ツール

SecLists
SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

WebStorm Mac版
便利なJavaScript開発ツール
