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

推薦する

uni-app で scss を使用するサンプル コード

遭遇した落とし穴私は午後中ずっと、uni-app で scss を使用する際の落とし穴を解決すること...

テーブルリストを破棄するには、標準のdl、dt、ddタグを使用します。

現在、ますます多くのフロントエンド開発者が、元のテーブル レイアウトを xHTML + CSS に置...

Linux でパスワードを入力せずに sudo コマンドを実行する方法

sudo コマンドを使用すると、信頼できるユーザーは別のユーザー (デフォルトでは root ユーザ...

フローティングをクリアするいくつかの方法(推奨)

1. 同じタイプの空の要素を追加し、要素の CSS 属性 clear:both; を設定します。 ...

CocosCreator MVCアーキテクチャの詳細な説明

概要この記事では、ゲームクライアントでよく使用される MVC アーキテクチャについて紹介します。ゲー...

ServerManager の起動時にデータベースに接続できないエラーを解決する方法

Servermanager 起動時の接続データベース エラーmgrstart.batを実行しますエラ...

Vue で wangeditor リッチテキスト編集を使用する際の問題

wangEditor は、JavaScript と CSS に基づいて開発された Web リッチ テ...

CSS3はマスク連打機能を実現する

最近Bステーションでスマートアンチブロッキング弾幕と呼ばれる弾幕エフェクトを見ました。これは伝説のマ...

モバイル開発における 1px ラインの理解と解決策

1pxの線が太くなる理由モバイルプロジェクトに取り組むとき、設計図に従って要素ノードのサイズとスタイ...

Vue のスロットとフィルターの詳細な説明

目次スロットスロットとは何ですか?スロットの内容コンパイルスコープフォールバックコンテンツ名前付きス...

CSS3を使って歌詞進行テキストカラー塗りつぶし変更の動的効果を実装するアイデアの詳細な説明

音楽を再生すると、曲が進むにつれて歌詞が徐々に色づきます。色は単語ごとに変わるのではなく、左から右へ...

WeChatアプレットタブの左右スライドスイッチ機能実装コード

効果画像: 1. はじめに独自のアプレットでこのような機能を実装する必要がある1. 核となる考え方ス...

Ubuntu Server のターミナルのウェルカム メッセージで広告を無効にする方法

最新の Ubuntu Server バージョンを使用している場合、ようこそメッセージに、Ubuntu...

MySQL5.7+ MySQL Workbenchのインストールと設定方法のグラフィックチュートリアル(MAC)

この記事では、主にMACオペレーティングシステムでのMySQL5.7とMySQLWorkbenchの...

jQueryは従業員情報の追加と削除の機能を実装します

この記事では、従業員情報の追加と削除の機能を実装するためのjQueryの具体的なコードを参考までに共...