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 サーバーと Windows システム間でファイルをアップロードおよびダウンロードする方法

背景: Linux サーバーのファイルのアップロードとダウンロード。 XShell+Xftp インス...

MySQL 外部キー制約の例の説明

MySQL の外部キー制約は、2 つのテーブル間のリンクを確立するために使用されます。 1 つのテー...

Vue3 + TypeScript 開発の概要

目次Vue3 + TypeScript 学習1. 環境設定1.1 最新のVue scaffoldin...

AngularパイプラインPIPEの紹介と使い方

序文PIPE、パイプラインと翻訳されます。 Angular パイプは、HTML コンポーネントで宣言...

CentOS7.3 での MySQL 8.0.13 のインストールと設定のチュートリアル

1. 基本環境1. オペレーティングシステム: CentOS 7.3 2. MySQL: 8.0.1...

MySQLデータ遅延ジャンプの問題の解決策

今日は、データベース遅延ジャンプに関する別の典型的な問題を分析しました。このプロセスでは、参考のため...

CentOS ベースの OpenStack 環境の展開に関する詳細なチュートリアル (OpenStack のインストール)

エフェクト表示: 環境準備コントローラーノード: 6GB 4時間60GB/30GB/30GB計算ノー...

Vue3 での watchEffect の使用に関する簡単な分析

序文誰もが vue2 の watch API に精通している必要があります。vue2 の vue イ...

vue+element カスタムクエリコンポーネント

この記事では主に Vue プロジェクトを紹介します。要素の導入を前提として、コンポーネントを 2 回...

特定の MySQL テーブルの完全データと増分データをメッセージ キューに同期する - ソリューション

目次1. 当初の需要2. 解決策3. 運河の導入と設置運河の仕組み建築インストール4. 検証1. 当...

JS オブジェクト配列の重複排除のための 3 つの方法の例と比較

目次1. 重複排除前後のデータの比較2. 使い方1. フィルターとマップを使用する2. 削減を使用す...

ログインと登録を実現するSpringboot+VUE

この記事の例では、ログインと登録を実装するためのspringboot+VUEの具体的なコードを参考ま...

Maven で tomcat8-maven-plugin プラグインを使用する詳細なチュートリアル

オンラインで多くの記事を検索しましたが、解決策は見つかりませんでした。次のように、tomcat7-m...

Linux で特定のユーザーにフォルダーのすべてのコンテンツを許可するにはどうすればよいですか?

【問題分析】 chown コマンドを使用できます。ここで ch は change (変更) を表し...

MySQLで最新のトランザクションIDを照会する方法

前に書いた内容: ビジネス ロジックの判断を行うために、最新のトランザクション ID を表示する必要...