Vue2.x における双方向バインディングの原理と実装

Vue2.x における双方向バインディングの原理と実装

Vue はObject.defineProperty()メソッドを使用してデータを乗っ取り、set と get を使用してデータの読み取りと書き込みを検出します。

https://jsrun.net/RMIKp/embedded/all/light

MVVM フレームワークには、主に、データが変更されたときにビューを更新することと、ビューが変更されたときにデータを更新することという 2 つの側面が含まれます。

ビューはデータを変更して更新します。input のようなタグの場合は、 oninputイベントを使用できます。

データが変更されたときにビューを更新するには、 Object.definProperty()setメソッドを使用してデータの変更を検出します。データが変更されると、この関数がトリガーされ、ビューが更新されます。

1. 実施プロセス

双方向バインディングを実装する方法がわかったので、まずデータをハイジャックして監視する必要があるため、すべての属性の変更を監視するためのObserver関数を設定する必要があります。

プロパティが変更された場合、サブスクライバーwatcherに通知して、データの更新が必要かどうかを確認する必要があります。サブスクライバーが複数ある場合は、これらのサブスクライバーを収集し、 observerwatcher間で均一に管理するために Dep が必要です。

監視する必要があるノードと属性をスキャンして解析するには、ディレクティブ パーサーのcompileも必要です。

したがって、プロセスはおそらく次のようになります。

  • すべてのプロパティをハイジャックして監視し、変更が発生した場合にサブスクライバーに通知するObserverを実装します。
  • サブスクライバーWatcherを実装します。プロパティの変更通知を受け取ったら、対応する関数を実行してビューを更新します。これらのWatcherを収集するには、 Dep を使用します。
  • パーサーCompileを実装して、ノードの関連命令をスキャンおよび解析し、初期化テンプレートに従って対応するサブスクライバーを初期化します。

2. オブザーバーを表示する

Observerはデータ リスナーです。コア メソッドはObject.defineProperty()を使用して、監視対象のすべてのプロパティにsettergetterメソッドを再帰的に追加することです。

var ライブラリ = {
  本1: {
    名前: ""、
  },
  本2: "",
};
観察(ライブラリ);
library.book1.name = "vue authoritative guide"; // プロパティ名は監視されており、現在の値は "vue authoritative guide" です
library.book2 = "そのような本はありません"; // 属性 book2 は監視されており、現在の値は "そのような本はありません" です

// データの検出機能を追加 defineReactive(data, key, val) {
  observe(val); // すべてのサブ属性を再帰的に走査する let dep = new Dep(); // 新しい dep を作成する
  Object.defineProperty(データ、キー、{
    列挙可能: true、
    設定可能: true、
    取得: 関数() {
      if (依存ターゲット) {
        // サブスクライバーを追加するかどうかを決定します。これは最初の 1 回のみ必要で、次回は必要ありません。詳細については、Watcher 関数を参照してください。 dep.addSub(Dep.target); // サブスクライバーを追加します }
      戻り値:
    },
    設定: 関数(newVal) {
      if (val == newVal) return; // 値が変更されていない場合は戻ります
      val = 新しいVal;
      コンソール.log(
        「プロパティ」+ キー + 「」が監視されており、現在の値は次のとおりです: 「」+ newVal.toString() + 「」」
      );
      dep.notify(); // データが変更された場合は、すべてのサブスクライバーに通知します。
    },
  });
}

// 監視オブジェクトのすべてのプロパティ function observe(data) {
  if (!data || typeof data !== "object") {
    return; // オブジェクトでない場合は戻ります
  }
  Object.keys(データ).forEach(関数(キー) {
    defineReactive(データ、キー、データ[キー]);
  });
}
// Dep は、サブスクライバーを収集し、プロパティが変更されたときに更新関数をトリガーする役割を担います。
関数Dep() {
  this.subs = {};
}
派生プロトタイプ = {
  addSub: 関数(sub) {
    this.subs.push(sub);
  },
  通知: 関数() {
    this.subs.forEach((sub) => sub.update());
  },
};

アイデア分析では、サブスクライバー メッセージを収容できるサブスクライバー Dep が必要です。これは、サブスクライバーを収集し、属性が変更されたときに対応する更新機能を実行するために使用されます。

コードから、 getterにサブスクライバー Dep を追加するのは、初期化時にWatcherをトリガーするためです。したがって、サブスクライバーが必要かどうかを判断する必要があります。

setterでは、データが変更されるとすべてのサブスクライバーに通知され、サブスクライバーは対応する関数を更新します。

これまでのところ、比較的完全なObserverが完成しました。次に、Watcher の設計を開始します。

3. ウォッチャーを実装する

サブスクライバーWatcher 、初期化中にサブスクライバー Dep に自身を追加する必要があります。リスナーObserver get 中に実行される Watcher 操作であることは既にわかっているので、 Watcherが初期化されるときに対応するサブスクライバー操作を追加するには、対応する get 関数をトリガーするだけで済みます。

では、get をトリガーするにはどうすればよいでしょうか? Object.defineProperty()すでに設定してあるので、それをトリガーするには対応するプロパティ値を取得するだけで済みます。

サブスクライバー Watcher が初期化されたときにのみ Dep.target にサブスクライバーをキャッシュし、正常に追加された後に削除する必要があります。

関数ウォッチャー(vm, exp, cb) {
  this.cb = cb;
  this.vm = vm;
  this.exp = exp;
  this.value = this.get(); // サブスクライバーの操作に自分自身を追加します}

ウォッチャー.プロトタイプ = {
  更新: 関数() {
    これを実行してください。
  },
  実行: 関数() {
    var 値 = this.vm.data[this.exp];
    var oldVal = this.value;
    if (値 !== oldVal) {
      this.value = 値;
      this.cb.call(this.vm、値、oldVal);
    }
  },
  取得: 関数() {
    Dep.target = this; // ウォッチャーを追加するかどうかを決定するために自身をキャッシュします。
    var value = this.vm.data[this.exp]; // リスナーで get 関数を強制的に実行します。 Dep.target = null; // 自分自身を解放します。 return value;
  },
};

ここまでで、シンプルなWatcher設計が完了し、 ObserverWatcherを関連付けることで、シンプルな双方向バインディングを実装できます。

パーサーCompileまだ設計されていないため、最初にテンプレート データをハードコードできます。

コードを ES6 コンストラクターに変換してプレビューします。

https://jsrun.net/8SIKp/embed...

このコードはコンパイラを実装せず、バインドされた変数を直接渡すため、バインド用のノードにデータ ( name ) を設定し、ページ上でnew MyVueを実行して双方向バインドを実現します。

2 秒後に変更を加えます。ページも変更されたことがわかります。

// マイビュー
プロキシキー(キー) {
    var self = this;
    Object.defineProperty(this, キー, {
        列挙可能: false、
        設定可能: true、
        取得: 関数 proxyGetter() {
            self.data[キー]を返します。
        },
        設定: 関数 proxySetter(newVal) {
            self.data[キー] = newVal;
        }
    });
}

上記のコードの目的は、 this.dataのキーを this にプロキシして、 this.xxを使用してthis.data.xx簡単に取得できるようにすることです。

4. コンパイルを実装する

上記では双方向データバインディングを実装していますが、DOM ノードはプロセス全体で解析されるのではなく、固定的に置き換えられます。そのため、データを解析してバインドするにはパーサーが必要です。

パーサーcompileの実装手順:

  • テンプレート命令を解析し、テンプレート データを置き換えて、ビューを初期化します。
  • 対応する更新関数をテンプレートで指定されたノードにバインドし、対応するサブスクライバーを初期化します。

テンプレートを解析するには、まず DOM データを解析し、次に DOM 要素の対応する指示を処理する必要があります。したがって、DOM 操作全体は比較的頻繁に行われます。新しいフラグメントを作成し、必要な解析済みDOM fragmentに保存して処理することができます。

関数 nodeToFragment(el) {
  var フラグメント = document.createDocumentFragment();
  var child = el.firstChild;
  (子)の間{
    // Dom 要素をフラグメントに移動します。fragment.appendChild(child);
    子 = el.firstChild;
  }
  フラグメントを返します。
}

次に、各ノードをトラバースし、関連する命令とテンプレート構文を含むノードに対して特別な処理を実行する必要があります。まず、最も単純なテンプレート構文処理を実行し、正規表現を使用して「{{variable}}」の形式で構文を解析します。

関数compileElement(el){
    var childNodes = el.childNodes;
    var self = this;
    [].slice.call(childNodes).forEach(function(node) {
        var reg = /\{\{(.*)\}\}/; // {{xx}} に一致
        var テキスト = node.textContent;
        if (self.isTextNode(node) && reg.test(text)) { // この形式の命令であるかどうかを判断します {{}} self.compileText(node, reg.exec(text)[1]);
        }
        (node.childNodes && node.childNodes.length)の場合{
            self.compileElement(node); // 子ノードを再帰的に走査し続けます}
    });
},
関数compileText(ノード, exp) {
    var self = this;
    var initText = this.vm[exp];
    updateText(node, initText); // 初期化されたデータをビューに初期化します。 new Watcher(this.vm, exp, function (value) { // サブスクライバーを生成し、更新関数をバインドします。 self.updateText(node, value);
    });
},
関数 updateText (ノード, 値) {
    node.textContent = typeof value == 'undefined' ? '' : value;
}

最も外側のノードを取得した後、 compileElement関数を呼び出してすべての子ノードを判断します。ノードがテキスト ノードであり、{{}} 形式の指示に一致する場合は、コンパイルして対応するパラメーターを初期化します。

次に、データが変更されたときに対応する DOM を更新するために、現在のパラメータに対応する更新関数サブスクライバーを生成する必要があります。

これで、解析、初期化、コンパイルの 3 つのプロセスが完了します。

次に、双方向データ バインディングにテンプレート変数を使用するように myVue を変更します。

https://jsrun.net/K4IKp/embed...

5. 解析イベントを追加する

compileを追加すると、双方向のデータバインディングが基本的に完了します。次のステップは、 Compilev-modelv-onv-bindなどの解析とコンパイルのための命令をさらに追加することです。

v-model と v-on 解析を追加します。

関数コンパイル(ノード) {
  var nodeAttrs = node.attributes;
  var self = this;
  Array.prototype.forEach.call(nodeAttrs, function(attr) {
    var attrName = attr.name;
    if (isDirective(attrName)) {
      var exp = attr.value;
      var dir = attrName.substring(2);
      if (isEventDirective(dir)) {
        // イベント命令 self.compileEvent(node, self.vm, exp, dir);
      } それ以外 {
        // v-model ディレクティブ self.compileModel(node, self.vm, exp, dir);
      }
      node.removeAttribute(attrName); // 解析が完了したので、属性を削除します}
  });
}
// v-ディレクティブ解析関数 isDirective(attr) {
  attr.indexOf("v-") == 0 を返します。
}
// on: ディレクティブ解析関数 isEventDirective(dir) {
  dir.indexOf("on:") === 0 を返します。
}

上記のcompile関数は、現在のdomのすべてのノード属性をトラバースし、属性がディレクティブ属性であるかどうかを判定するために使用されます。ディレクティブ属性である場合は、対応する処理が実行されます (イベントはイベントとして監視され、データはデータとして監視されます...)

6. myVueのフルバージョン

MyVuemountedメソッドを追加し、すべての操作が完了したらそれを実行します。

クラスMyVue {
  コンストラクタ(オプション) {
    var self = this;
    オプションデータ
    this.methods = オプション.methods;
    Object.keys(this.data).forEach(function(key) {
      self.proxyKeys(キー);
    });
    これを観察します。
    新しいコンパイル(options.el、this);
    options.mounted.call(this); //すべてが完了したらマウントされた関数を実行します}
  プロキシキー(キー) {
    // this.data プロパティを this にプロキシします。var self = this;
    Object.defineProperty(this, キー, {
      列挙可能: false、
      設定可能: true、
      取得: 関数 getter() {
        self.data[キー]を返します。
      },
      設定: 関数 setter(newVal) {
        self.data[キー] = newVal;
      },
    });
  }
}

その後、テストすることができます。

https://jsrun.net/Y4IKp/embed...

プロセスをまとめてみましょう。この図をもう一度見てみると、さらに明確になります。

コードアドレスを表示できます: Vue2.x 双方向バインディングの原則と実装

これで、 Vue2.xの双方向バインディングの原則と実装に関するこの記事は終了です。Vue データの双方向バインディングの原則に関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • v-model 双方向バインディングデータを実装する vue カスタム コンポーネントのサンプル コード
  • フロントエンドフレームワーク Vue における親子コンポーネントデータの双方向バインディングの実装
  • Vue の双方向イベントバインディング v-model の原理についての簡単な説明
  • Vue2.0でデータの双方向バインディング機能をjsを使って実装する
  • 純粋な JS を使用して vue.js で双方向バインディング機能を実装する方法
  • Vue双方向バインディングの詳細な説明

<<:  MySQL スロークエリ関連パラメータの原理の分析

>>:  MySQLの大規模テーブル最適化ソリューションについての簡単な説明

推薦する

Dockerのインストール方法とDockerの4つのネットワークモードの詳細説明

1. Dockerをインストールするyum -y install docker-ioインストールが完...

CentOS 7 でゲートウェイを変更して IP を設定する方法の例

Centos7 バージョンをインストールするときに、外部ネットワークへの接続を選択すると、外部ネット...

MySQLの論理アーキテクチャに関する深い理解

MySQL は現在、ほとんどの企業や事業体で使用されているデータベースです。MySQL が使用される...

JavaScriptはパスワードボックスの検証情報を実装します

この記事では、パスワードボックスの検証情報を実装するためのJavaScriptの具体的なコードを例と...

MySQL マルチテーブルクエリの詳細な説明

よく食べて十分に休息を取るというのは簡単なことのように思えますが、実際に実行するのはそれほど簡単では...

RHEL8 /CentOS8 でマルチノード Elastic Stack クラスターを構築する方法

一般的に ELK スタックとして知られる Elastic スタックは、Elasticsearch、L...

Linuxで同一ファイルを見つける方法

コンピュータを使用すると、システム内に大量のゴミが生成されます。最も一般的なケースは、同じファイルが...

Linux C ログ出力コード テンプレート サンプル コード

序文この記事は主に Linux C でのログ出力コード テンプレートに関する関連コンテンツを紹介し、...

JavaScript 配列 sort() メソッドの基本的な使い方と落とし穴

序文日常のコード開発では、配列のソートに関連する操作が多数あります。JavaScript では、so...

複数の Tomcat を展開して起動し、プロジェクトを移行する方法を 1 つの記事で学習します。

目次tomcatをデプロイする1.ダウンロードして解凍する2. 設定ファイルを変更する移植プロジェク...

CSS と HTML とフロントエンド テクノロジーのレイヤー図

JavascriptとDOMの関係は非常に曖昧で、CSSやHTMLのフロントエンド技術層も理解してい...

Linux の crw、brw、lrw などのファイル属性は何ですか?

ファイルとは何ですか?すべてのファイルは実際には文字列のストリームですが、適切な解析方法を使用すると...

ページングクリックコントロールを実装するネイティブJS

これは、ネイティブJSを使用してページングクリックコントロールを実装する必要がある面接の質問です。参...

Vue でコミュニケーションを実装する 8 つの方法

目次1. コンポーネント通信1. Props 親コンポーネント ---> 子コンポーネント通信...

CSS のオーバーフロー:hidden エラーの解決方法

失敗の原因今日、カルーセルを書いていたときに、overflow;hidden; が失敗する可能性があ...