Vue2.0の双方向データバインディング原則を手動で実装する

Vue2.0の双方向データバインディング原則を手動で実装する

一言で言えば: データハイジャック (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)を定義します。
、this.newDepIds)

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 をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Vue2.0はv-modelを使用して、コンポーネントプロパティの双方向バインディングの美しいソリューションを実装します。
  • Vue2.0双方向バインディングの実装原理を分析する
  • Vue2.0 データの双方向バインディングとフォーム Bootstrap+Vue コンポーネント
  • Vue2.0はコンポーネントデータの双方向バインディングを実装します
  • Vue2.0/3.0双方向データバインディングの実装原理の詳細説明
  • Vue2.0でデータの双方向バインディング機能をjsを使って実装する

<<:  MySQL マスタースレーブレプリケーションの実践の詳細説明 - ログポイントに基づくレプリケーション

>>:  Nginx 環境での WordPress マルチサイト構成の詳細な説明

推薦する

Vue+elementを使用してページ上部のタグを実装する方法の詳細な説明

目次1. ページレンダリング2. タグを切り替える3. タグを削除するこのようなタグはどのように記述...

MySQL5.7.03 上位バージョンから MySQL 5.7.17 への置き換えインストール プロセスと見つかった問題の解決策

1. インストール方法は? 1. [実行] -> [cmd] と入力して、小さな黒いウィンドウ...

Postman に基づく HTTP インターフェース テスト プロセスの分析

偶然、素晴らしい人工知能のチュートリアルを発見したので、みんなと共有せずにはいられませんでした。この...

MySQLログに関する知識のまとめ

目次SQL実行順序ビンログ何ですかいつ生産されるのか何の役に立つんだディスクはいつドロップされますか...

HTMLヘッダータグの使用に関する詳細な説明

HTMLはヘッドとボディの2つの部分で構成されています** ヘッド内のタグはヘッドタグです** タイ...

MySQL: データの整合性

データ整合性は、エンティティ整合性、ドメイン整合性、参照整合性に分けられます。参照整合性:参照整合性...

SQL 集計、グループ化、並べ替え

目次1. 集計クエリ1. COUNT関数2. SUM関数3. AVG関数4. MAX関数とMIN関数...

ウェブサイトアイコンを追加するにはどうすればいいですか?

最初のステップは、アイコン作成ソフトウェアを準備することです。まず、いわゆるアイコンは拡張子 .ic...

docker pullがリセットされる問題を解決する

この記事では、docker pull がリセットされる問題を解決する方法を紹介し、皆さんと共有します...

MySQL タイムスタンプ比較クエリで遭遇する落とし穴と解決策

目次タイムスタンプ比較クエリで遭遇する落とし穴タイムスタンプクエリ範囲の問題タイムスタンプ比較クエリ...

Vueのフィルターとディレクティブの詳細な説明

目次vueカスタムディレクティブグローバル指令ローカル指示使用フック関数(両方ともオプション)使用方...

Linuxでブーストライブラリをインストールするための完全な手順

序文Boost ライブラリは、標準ライブラリのバックアップとして機能し、C++ 標準化プロセスの開発...

MySQL スローログ実践のまとめ

遅いログクエリ機能スロー ログ クエリの主な機能は、設定された時間しきい値を超える SQL ステート...

インデックスを使用して数千万のデータを持つ MySQL のクエリ速度を最適化する

1. インデックスの役割一般的に言えば、インデックスは本の目次に相当します。条件に基づいてクエリを実...

jQueryのコア機能とイベント処理の詳細な説明

目次イベントページの読み込みイベント委任イベントの切り替えイベント要約するイベントページの読み込み1...