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 マルチサイト構成の詳細な説明

推薦する

Linux システムのシャットダウンコマンドの違いと使い方の詳細な説明

Linux システムのシャットダウン コマンドは何ですか? Liangxu Tutorial Net...

SQLは、隣接する2行のデータに対して加算、減算、乗算、除算の演算を実行します。

SQL は、データを特定の順序で並べ替え、特定のフィールドでグループ化した後、隣接する 2 つのデ...

MySQL でよく使用されるデータベースとテーブル シャーディング ソリューションの概要

目次1. データベースのボトルネック2. サブライブラリとサブテーブル2. 横長テーブル3. 垂直サ...

Nginx ログ管理の概要

Nginx ログの説明アクセス ログを通じて、ユーザーの地理的起源、ジャンプ元、使用端末、特定の U...

win10 64 ビット システムに複数の JDK バージョンをインストールする際の切り替え問題と解決策の概要

コンピューターにmyeclipse2017とidea2017がインストールされているため、ideaが...

MySQLがlocalhost経由でデータベースに接続できない問題に対する完璧な解決策

問題:あるサーバー上の PHP プログラムは、localhost アドレス経由でデータベースに接続で...

Docker に MySQL をデプロイする例

目次1 コンテナクラウドとは何ですか? 2 Dockerの紹介3 dockerを使ってMySQLをイ...

phpstudy から Linux への MySQL の移行に関するチュートリアル

プロジェクトの目的元のWindows環境でphpstudyを使用して構築されたMySQL 5.5.5...

絵文字と問題解決のためのMySQL/Javaサーバーサポートの詳細な説明

この記事では、絵文字用の MySQL Java サーバーのサポートと問題解決方法について説明します。...

MySQL関連のツールをいくつかお勧めします

序文:インターネット技術の継続的な発展に伴い、MySQL 関連のエコシステムはますます充実し、ますま...

MySQL マスターとスレーブの不整合とその解決策の詳細な説明

1. MySQL マスタースレーブ非同期1.1 ネットワーク遅延MySQLのマスタースレーブレプリケ...

HTMLはマウスをホバーしたときにテキストを表示するためにtitle属性を使用します。

コードをコピーコードは次のとおりです。 <a href=# title="ここに表示...

js で虫眼鏡効果を実現するためのアイデアとコード

この記事の例では、虫眼鏡効果を実現するためのjsの具体的なコードを参考までに共有しています。具体的な...

Tencent Cloud Server での Jenkins の設定方法の詳細

目次1. Tencent Cloud Serverに接続する2. 環境整備Jenkinsのデプロイメ...

MySQL でのデータベース間クエリの例

序文MySQL では、クロスデータベース クエリは主に 2 つの状況に分けられます。1 つは同じサー...