一言で言えば: データハイジャック (Object.defineProperty) + パブリッシュ・サブスクライブモード双方向データバインディングには、3 つのコアモジュール (dep、observer、watcher) があります。これらがどのように接続されているかを 1 つずつ紹介します。 双方向データ バインディングの原理と、それらが相互に関連付けられる仕組みをより深く理解するために、まずパブリッシュ/サブスクライブ モデルを確認しましょう。 1. まずパブリッシュ・サブスクライブモデルとは何かを理解するコード上で直接: 双方向データバインディングの原理をよりよく理解するのに役立つシンプルなパブリッシュサブスクライブモデル //パブリッシュおよびサブスクライブモード関数 Dep() { this.subs = []//依存関係(つまり、モバイルウォッチャーインスタンス)を収集します。 } Dep.prototype.addSub = function (sub) { // サブスクライバーを追加 this.subs.push(sub); // 実際に追加されるのはウォッチャーインスタンスです} Dep.prototype.notify = function (sub) { //Publish、このメソッドは配列をトラバースし、各サブスクライバーの更新メソッドを実行させるために使用されます this.subs.forEach((sub) => sub.update()) } 関数ウォッチャー(fn) { this.fn = fn; } Watcher.prototype.update = function () { //各インスタンスがこのメソッドを継承できるように更新プロパティを追加します this.fn(); } ウォッチャー = new ウォッチャー(関数 () { 警告(1) }); //サブスクリプション let dep = new Dep(); dep.addSub(watcher); //依存関係を追加し、サブスクライバーを追加します dep.notify(); //公開し、各サブスクライバーの更新メソッドを実行します 2. new Vue() は何を行いましたか?双方向データバインディングを説明するために <テンプレート> <div id="アプリ"> <div>obj.text の値:{{obj.text}}</div> <p>単語の値:{{word}}</p> <input type="text" v-model="word"> </div> </テンプレート> <スクリプト> 新しいVue({ el: "#app", データ: { オブジェクト: { テキスト: "上"、 }, 単語:「学習」 }, 方法:{ // ... } }) </スクリプト> Vue コンストラクターは何をするのでしょうか? 関数Vue(オプション = {}) { this.$options = options; //パラメータを受け取る var data = this._data = this.$options.data; observer(data); //データ内のデータを再帰的にバインドする for (let key in data) { val = data[キー]とします。 オブザーバー(val); Object.defineProperty(this, キー, { 列挙可能: true、 得る() { this._data[キー]を返します。 }, set(newVal) { this._data[キー] = newVal; } }) } 新しいコンパイル(options.el、これ) }; new Vue({…}) コンストラクターでは、最初にパラメーター オプションを取得し、次にパラメーターのデータを現在のインスタンスの _data プロパティに割り当てます (this._data = this.$options.data)。ここで問題になるのは、なぜ次のようなトラバーサルが行われるのかということです。まず、データを操作するときには、this._data.word ではなく this.word からデータを取得するので、マッピングを作成します。データを取得するとき、this.word は実際には this._data.word の値を取得します。これをプロジェクトに出力して確認できます。 1. 次に、オブザーバーメソッドが何をするのかを見てみましょう 関数オブザーバー(データ) { if (typeof data !== "object") return; return new Observer(data); //インスタンスを返す} 関数 Observer(データ) { let dep = new Dep(); // dep インスタンスを作成します for (let key in data) { // データを再帰的にバインドします let val = data[key]; オブザーバー(val); Object.defineProperty(データ、キー、{ 列挙可能: true、 得る() { Dep.target && dep.depend(Dep.target); //Dep.target は Watcher のインスタンスです。 return val; }, set(newVal) { (newVal === val)の場合{ 戻る; } val = 新しいVal; オブザーバー(newVal); dep.notify() //すべてのメソッドを実行させる} }) } } オブザーバー コンストラクターでは、まず、データ ハイジャックをトリガーする get メソッドと set メソッドとして dep = new Dep() を設定し、依存関係を収集して公開時に呼び出します。主な操作は、Object.defineProperty を介してデータを再帰的にバインドし、依存関係を収集して更新を公開するために、getter/setter を使用してデフォルトの読み取りと書き込みを変更することです。 2. Compileが具体的に何をするのか見てみましょう 関数Compile(el, vm) { vm.$el = document.querySelector(el); let fraction = document.createDocumentFragment(); //オブジェクト型のドキュメントフラグメントを作成しますwhile (child = vm.$el.firstChild) { フラグメント.appendChild(子); }; //while ループを使用してすべてのノードをドキュメント フラグメントに追加し、続いてドキュメント フラグメントに対する操作を実行し、最後にドキュメント フラグメントをページに追加します。ここで非常に重要な機能は、appendChid メソッドを使用して元の DOM ツリーのノードをフラグメントに追加すると、元のノードが削除されることです。 フラグメントを置き換えます。 関数 replace(フラグメント) { Array.from(fragment.childNodes).forEach((node) =>{//すべてのノードをループします。let text = node.textContent; reg = /\{\{(.*)\}\}/ とします。 if (node.nodeType === 3 && reg.test(text)){//現在のノードがテキストノードであるかどうか、および {{obj.text}} の出力方法に準拠しているかどうかを判断します。条件が満たされている場合は、双方向データバインディングであるため、サブスクライバー (ウォッチャー) を追加する必要があることを意味します。 console.log(正規表現$1); //obj.text let arr = RegExp.$1.split("."); // 値を簡単に取得できるように配列 [obj, text] に変換します。let val = vm; arr.forEach((key) => { //値を実現する this.obj.text val = val[キー]; }); 新しいウォッチャー(vm, RegExp.$1, 関数(newVal) { node.textContent = text.replace(/\{\{(.*)\}\}/, newVal) }); node.textContent = text.replace(/\{\{(.*)\}\}/, val); //ノードコンテンツに初期値を割り当てる} if (node.nodeType === 1) { //要素ノードであることを示します let nodeAttrs = node.attributes; Array.from(nodeAttrs).forEach((item) => { if (item.name.indexOf("v-") >= 0){//v-model命令かどうかを判断 node.value = vm[item.value]//ノードに値を割り当てる} // サブスクライバーを追加 new Watcher(vm, item.value, function (newVal) { ノード値 = vm[アイテム値] }); node.addEventListener("入力", 関数(e) { newVal = e.target.value; とします。 vm[item.value] = newVal; }) }) } if (node.childNodes) { //このノードにはまだ子要素があるので、再帰的にreplace(node); } }) } //ページ上のドキュメントが消えてしまったため、ドキュメントのフラグメントをページに配置する必要があります。vm.$el.appendChild(fragment); } コンパイル まず、DocumentFragment について説明します。これは DOM ノード コンテナです。複数のノードを作成すると、各ノードはドキュメントに挿入されるときにリフローをトリガーします。つまり、ブラウザは複数回リフローする必要があり、パフォーマンスが非常に低下します。ドキュメント フラグメントを使用するには、最初に複数のノードをコンテナに配置し、次にコンテナ全体を直接挿入します。ブラウザは 1 回だけリフローします。 Compile メソッドは、まずドキュメント フラグメントのすべてのノードをトラバースします。1. テキスト ノードであるかどうか、および {{obj.text}} の二重中括弧出力メソッドに準拠しているかどうかを判断します。条件が満たされている場合は、双方向データ バインディングであることを意味し、サブスクライバー (ウォッチャー) を追加する必要があります (新しいウォッチャー (vm、動的にバインドされた変数、コールバック関数 fn))。2. 要素ノードであるかどうか、および属性に v-model 命令が含まれているかどうかを判断します。条件が満たされている場合は、双方向データ バインディングであることを意味し、トラバースが完了するまで、サブスクライバー (ウォッチャー) を追加する必要があります (新しいウォッチャー (vm、動的にバインドされた変数、コールバック関数 fn))。 最後に、ドキュメントの断片をページに配置することを忘れないでください 3.Depコンストラクタ(依存関係の収集方法)var uid = 0; //パブリッシュとサブスクライブ関数 Dep() { this.id = uid++; this.subs = []; } Dep.prototype.addSub = function (sub) { // サブスクライブ this.subs.push(sub); // 実際に追加されるのはウォッチャーインスタンスです} Dep.prototype.depend = function () { // サブスクリプション マネージャー if(Dep.target){//Dep.target が存在する場合にのみ追加されます Dep.target.addDep(this); } } Dep.prototype.notify = function (sub) { //パブリッシュし、配列を走査して各サブスクライバーの更新メソッドを実行させる this.subs.forEach((sub) => sub.update()) } Dep コンストラクター内には id と subs があり、id=uid++ です。id は dep オブジェクトの一意の識別子として使用され、subs はウォッチャーを保存する配列です。 depend メソッドはサブスクリプション マネージャーです。現在のウォッチャーの addDep メソッドを呼び出してサブスクライバーを追加します。データ ハイジャック (Object.defineProperty) の get メソッドがトリガーされると、サブスクライバーを追加するために Dep.target && dep.depend(Dep.target) が呼び出されます。データが変更されたときにデータ ハイジャック (Object.defineProperty) の set メソッドがトリガーされると、dep.notify メソッドが呼び出されて操作が更新されます。 4. Watcher コンストラクターは何をするのですか?関数ウォッチャー(vm, exp, fn) { this.fn = fn; this.vm = vm; this.exp = exp // this.newDeps = []; this.depIds = 新しい Set(); this.newDepIds = 新しい Set(); Dep.target = this; //this は現在の (Watcher) インスタンスを参照します let val = vm; arr = exp.split(".") とします。 arr.forEach((k) => { //this.obj.textの値を取得する val = val[k] // this.obj.text の値を取得すると、データハイジャックの get メソッドがトリガーされ、現在のサブスクライバー (ウォッチャーインスタンス) が依存関係に追加されます}); 依存関係ターゲット = null; } Watcher.prototype.addDep = 関数 (dep) { var id = dep.id; if (!this.newDepIds.has(id)){ this.newDepIds.add(id); this.newDeps.push(dep); if(!this.depIds.has(id)){ dep.addSub(これを); } } } Watcher.prototype.update = function () { //これは、バインドされた各メソッドが更新プロパティを追加する方法です。let val = this.vm; arr = this.exp.split("."); とします。 arr.forEach((k) => { val = val[k] //this.obj.textの値を取得し、更新操作のためにfnに渡します}); this.fn(val); // 新しい値を渡す} Watcher コンストラクターは何をするのでしょうか? 1 パラメータを受け取り、いくつかのプライベートプロパティ(this.newDep、this.depIds)を定義します。 2. Dep.target = this の場合、データ値操作はパラメータを通じて実行され、Object.defineProperty の get メソッドがトリガーされ、サブスクライバー マネージャ (dep.depend()) を通じてサブスクライバーが追加され、次に Dep.target = null が空に設定されます。 3. プロトタイプのaddDepは、一意の識別子idといくつかのプライベートプロパティを使用して、サブスクライバーが繰り返し追加されるのを防ぎます。 4. 更新メソッドは、データが更新されると dep.notify() が実行され、サブスクライバーの更新メソッドがトリガーされてパブリッシュ更新操作が実行されます。 総括するvue2.0 では、双方向データ バインディングは Observer、Watcher、Dep の 3 つの部分で構成されます。 1. まず、Object.defineProperty() を使用してデータ ハイジャックを再帰的に実装し、サブスクライバー コレクションの管理配列 dep を各プロパティに割り当てます。 2. コンパイル時にドキュメントフラグメントを作成し、すべてのノードをドキュメントフラグメントに追加し、ドキュメントフラグメントのすべてのノードをトラバースし、{{}}、v-model、新しいWatcher()インスタンスの場合は、インスタンスをdepのsubs配列に追加します。 3. 値の最終的な変更により、Object.defineProperty() の set メソッドがトリガーされ、その中で dep.notify() が実行され、その後、すべてのサブスクライバーの update メソッドがループで呼び出され、ビューが更新されます。 これで、Vue2.0 の双方向データバインディングの原則を手動で実装する方法に関するこの記事は終了です。Vue2.0 の双方向データバインディングに関するより関連性の高いコンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:
|
<<: MySQL マスタースレーブレプリケーションの実践の詳細説明 - ログポイントに基づくレプリケーション
>>: Nginx 環境での WordPress マルチサイト構成の詳細な説明
目次1. ページレンダリング2. タグを切り替える3. タグを削除するこのようなタグはどのように記述...
1. インストール方法は? 1. [実行] -> [cmd] と入力して、小さな黒いウィンドウ...
偶然、素晴らしい人工知能のチュートリアルを発見したので、みんなと共有せずにはいられませんでした。この...
目次SQL実行順序ビンログ何ですかいつ生産されるのか何の役に立つんだディスクはいつドロップされますか...
HTMLはヘッドとボディの2つの部分で構成されています** ヘッド内のタグはヘッドタグです** タイ...
データ整合性は、エンティティ整合性、ドメイン整合性、参照整合性に分けられます。参照整合性:参照整合性...
目次1. 集計クエリ1. COUNT関数2. SUM関数3. AVG関数4. MAX関数とMIN関数...
最初のステップは、アイコン作成ソフトウェアを準備することです。まず、いわゆるアイコンは拡張子 .ic...
この記事では、docker pull がリセットされる問題を解決する方法を紹介し、皆さんと共有します...
目次タイムスタンプ比較クエリで遭遇する落とし穴タイムスタンプクエリ範囲の問題タイムスタンプ比較クエリ...
目次vueカスタムディレクティブグローバル指令ローカル指示使用フック関数(両方ともオプション)使用方...
序文Boost ライブラリは、標準ライブラリのバックアップとして機能し、C++ 標準化プロセスの開発...
遅いログクエリ機能スロー ログ クエリの主な機能は、設定された時間しきい値を超える SQL ステート...
1. インデックスの役割一般的に言えば、インデックスは本の目次に相当します。条件に基づいてクエリを実...
目次イベントページの読み込みイベント委任イベントの切り替えイベント要約するイベントページの読み込み1...