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の大規模テーブル最適化ソリューションについての簡単な説明

推薦する

Linux コマンドラインのワイルドカードとエスケープ文字の実装

ハードディスクのファイル属性のバッチ表示など、特定の種類のファイルに対してバッチ操作を実行する場合、...

ES6 における Object.assign() の使い方の詳細な説明

目次2. 目的2.1 オブジェクトにプロパティを追加する2.3 オブジェクトの複製2.4 複数のオブ...

Azure Container Registry を使用してイメージを保存する際の問題

Azure Container Registry は、Docker Registry 2.0 仕様に...

MySQL フルテキスト インデックス、ジョイント インデックス、Like クエリ、JSON クエリのうち、どれが高速ですか?

目次クエリの背景1. クエリをいいね2. JSON関数クエリ3. 共同インデックスクエリ4. 全文イ...

MySQL 5.7 における基本的な JSON 操作ガイド

序文プロジェクトのニーズにより、ストレージ フィールドは JSON 形式で保存されます。プロジェクト...

VMware Workstation での VMware vSphere のセットアップ (グラフィック チュートリアル)

VMware vSphere は、業界をリードする最も信頼性の高い仮想化プラットフォームです。 v...

Docker コンテナのネットワーク設定によく使われるコマンドの詳しい説明

基本的なネットワーク構成Docker はイメージに基づいて複数のコンテナを「開く」ことができ、各コン...

MySQL アーキテクチャのナレッジポイントの概要

1. データベースとデータベースインスタンスMySQL の研究では、データベースとデータベース イン...

TypeScript をインストール、使用、自動コンパイルする方法に関するチュートリアル

1. TypeScriptの紹介前回の記事ではTypeScriptのインストール、使い方、自動コンパ...

JavaScript 配列の重複排除とフラット化関数の紹介

目次1. 配列の平坦化(配列の次元削減とも呼ばれる)方法1: 削減メソッドを使用する方法2: スタッ...

Vueプロジェクトがグラフィック検証コードを実装

この記事の例では、グラフィック検証コードを実装するためのVueプロジェクトの具体的なコードを参考まで...

JS での filter() 配列フィルターの使用

目次1. はじめに2. 方法の紹介3. 使用例要約する1. はじめに配列フィルターは、フロントエンド...

Linux環境でユーザーにsudo権限を追加する方法

sudo 設定ファイルsudo のデフォルトの設定ファイルは /etc/sudoers です。一般的...

HTML での位置の使用に関する簡単な紹介

昨日 HTML を少し学んだばかりで、JD.com の検索バーを作るのが待ちきれませんでした。 作っ...

Mysql | ワイルドカード(%、_ など)を使用したファジークエリの詳細な説明

ワイルドカードのカテゴリ: %パーセント ワイルドカード: 任意の文字が任意の回数出現できることを示...