jsは双方向データバインディング(アクセサ監視)を実現します

jsは双方向データバインディング(アクセサ監視)を実現します

この記事の例では、双方向データバインディングを実現するためのjsの具体的なコードを参考までに共有しています。具体的な内容は次のとおりです。

双方向バインディング:

双方向バインディングはMVVMモデルに基づいています: model-view-viewModel

モデル:ビジネスロジックとデータベースとのやり取りを担当するモデル層
ビュー:ビュー層は、データモデルとUIを組み合わせてページに表示する役割を担います。
viewModel:モデルとビューの間の通信ブリッジとして機能するビューモデルレイヤー

双方向バインディングの意味は、モデルデータが変更されるとビューレイヤーに通知され、ユーザーがビューレイヤーでデータを変更すると、それがモデルレイヤーに反映されることです。

双方向データバインディングの利点は、データ操作のみに焦点を合わせ、DOM 操作を削減できることです。

Vue.js 実装の原則はアクセサー監視を使用することです。そのため、ここでもアクセサー監視を使用して、単純な双方向データ バインディングを実現します。

アクセサー監視の実装では、主に JavaScript のネイティブ メソッド Object.defineProperty を使用します。このメソッドを使用すると、オブジェクトにアクセサー プロパティを追加できます。オブジェクトのプロパティがアクセスされるか、値が割り当てられると、アクセサー プロパティがトリガーされます。したがって、この考え方を使用して、アクセサー プロパティにハンドラーを追加できます。

ここでは、まず入力タグの簡単な双方向データ バインディング プロセスを実装し、まず双方向データ バインディングとは何かについて一般的な理解を深めます。

<入力タイプ="テキスト">

<スクリプト>
// 入力ボックスオブジェクトを取得します。let input = document.querySelector('input');
// オブジェクトのプロパティの変更を監視するためにプロトタイプ チェーンなしでオブジェクトを作成します。let model = Object.create(null);
// マウスが入力ボックスから離れると、ビューレイヤーのデータはモデルレイヤーのデータの変更を通知します input.addEventListener('blur',function() {
    モデル['user'] = this.value;
})

// モデル レイヤーのデータが変更されたら、その変更をビュー レイヤーに通知します。
Object.defineProperty(モデル、'ユーザー'、{
    セット(v) {
        ユーザー = v;
        入力値 = v;
    },
    得る() {
        ユーザーを返します。
    }
})
</スクリプト>

上記のコードでは、まずInputタグオブジェクトを取得し、次にinput要素オブジェクトにリスニングイベント(blur)を追加します。イベントがトリガーされたとき、つまりビューレイヤーが変更されたときは、モデルレイヤーにデータの更新を通知する必要があります。ここでモデルレイヤーはプロトタイプのない空のオブジェクトを使用します(空のオブジェクトを使用する理由は、プロトタイプチェーンの存在により、特定の属性を取得するときにデータの誤解を避けるためです)。

Object.defineProperty メソッドを使用して、オブジェクトの指定されたプロパティのアクセサー プロパティを追加します。オブジェクトのプロパティが変更されると、セッター アクセサーがトリガーされます。ここで、ビュー レイヤー データに値を割り当て、ビュー レイヤー データを更新できます。ここでのビュー レイヤーは、Input タグの属性値を参照します。

効果を見てみましょう:

テキスト ボックスにデータを入力し、コンソールに model.user と出力して、データがモデル レイヤーに影響を与えたことを確認します。

次に、コンソールでモデル レイヤーのデータを手動で変更します: model.user = '9090';
この時点で、データテキストボックスもそれに応じて変更され、ビューレイヤーに影響を与えていることがわかります。

さて、テキスト ボックスの最も単純な双方向データ バインディングが実装されました。上記のケースから、次の実装ロジックを見つけることができます。

①. ビュー層からモデルへのデータ通信を実現するには、ビュー層のデータ変更とビュー層の値を知る必要がありますが、テキストボックスの入力値を取得できるinputタグのvalue属性などの組み込み属性がない限り、通常はタグ自体の値を取得する必要があります。

②. Object.definePropertyを使用して、モデル層からビュー層への通信を実装します。データが変更されると、アクセサープロパティセッターがすぐにトリガーされ、プロパティを使用するすべてのビュー層に現在のデータを更新するように通知できます(オブザーバー)

③. Object.defineProperty はオブジェクトのプロパティに対して有効になるアクセサー機能であるため、バインドされたデータはオブジェクトのプロパティである必要があります。

上記の要約に基づいて、vue.js に似た双方向データ バインディング モードを設計できます。
ビュー レイヤーからモデル レイヤーへのデータ通信を実装するには、カスタム命令を使用します。モデル レイヤーからビュー レイヤーへのデータ通信を実装するには、Object.defineProperty を使用します。

ここでの実装には、主に 3 つの機能が含まれます。

  • _observer:データを処理し、各属性のゲッター/セッターを書き換えます
  • _compile:カスタム命令を解析し (ここでは e-bind/e-click/e-model のみが関係します)、解析プロセス中にネイティブ処理イベントをノードにバインドし、ビュー レイヤーからモデル レイヤーへのバインディングを実装します。
  • Watcher:モデルとビューの間の中間ブリッジとして、モデルが変更されたときにビュー レイヤーをさらに更新します。

実装コード:

html

<!DOCTYPE html>
<html lang="ja">
<ヘッド>
    <メタ文字セット="UTF-8">
    <title>双方向データバインディング</title>
    <スタイル>
        #アプリ {
            テキスト配置: 中央;
        }
    </スタイル>
    <script src="/js/eBind.js"></script>
    <スクリプト>
        window.onload = 関数(){
           ebind = new EBind({
                el: '#app',
                データ: {
                    番号: 0,
                    人:
                        年齢: 0
                    }
                },
                メソッド: {
                    増分: 関数 () {
                        this.number++;
                    },
                    addAge: 関数 () {
                        this.person.age++;
                    }
                }
            })
        }
    </スクリプト>
</head>
<本文>
<div id="アプリ">
    <フォーム>
        <input type="text" e-model="number">
        <button type="button" e-click="increment">増加</button>
    </フォーム>
    <input e-model="数値" type="テキスト">
    <フォーム>
        <input type="text" e-model="人.年齢">
        <button type="button" e-click="addAge">追加</button>
    </フォーム>
    <h3 e-bind="人.年齢"></h3>
</div>
</本文>
</html>

javascript の eBind.js を参照してください。

関数EBind(オプション) {
    this._init(オプション);
}

// 指定されたカスタムパラメータに従って双方向データバインディングを初期化します EBind.prototype._init = function (options) {
    // オプションは初期化のためのデータで、el、データ、メソッドが含まれます
    this.$options = オプション;

    // el は管理する必要がある Element オブジェクトです。el:#app this.$el:id は app の Element オブジェクトです。this.$el = document.querySelector(options.el);

    // データ this.$data = options.data;

    //メソッド this.$methods = options.methods;

    // _bindingはモデルとビュー、つまりWachterインスタンス間のマッピングを格納します。モデルが更新されると、対応するビューも更新されます
    this._binding = {};

    // this.$data の get メソッドと set メソッドをオーバーライドします。this._obverse(this.$data);

    // 解析命令 this._compile(this.$el);
}


// この機能は、this.$data 内のすべての属性を監視し、アクセサを監視し、モデルからビュー層へのデータ通信を実現します。モデルレイヤーが変更されたら、ビューレイヤーに通知します。EBind.prototype._obverse = function (currentObj, completeKey) {
    // コンテキストを保存 var _this = this;

    // currentObj は get/set を書き換える必要があるオブジェクトです。Object.keys はオブジェクトのプロパティを取得し、配列を取得します。// 配列を走査します Object.keys(currentObj).forEach(function (key) {

        // オブジェクト自身のプロパティが監視されている場合のみ if (currentObj.hasOwnProperty(key)) {

            // オブジェクトのプロパティの場合は、 person.age の形式で保存する必要があります。 var completeTempKey = completeKey ? completeKey + '.' + key : key;

            // 監視する必要がある属性の関連付けを確立する_this._binding[completeTempKey] = {
                _directives: [] // このデータが使用されるすべての場所を保存します};

            // 現在の属性の値を取得します。var value = currentObj[key];

            // 値がオブジェクトの場合、それを反復処理して各オブジェクト属性を完全に監視します。if (typeof value == 'object') {
                _this._obverse(値、completeTempKey);
            }

            var バインディング = _this._binding[completeTempKey];

            // オブジェクトの各プロパティの get と set を変更し、get と set に処理イベントを追加します。Object.defineProperty(currentObj, key, {
                列挙可能: true、
                configurable: true, // デフォルトを false にしないようにする
                得る() {
                    戻り値;
                },
                セット(v) {
                    // value は現在の属性の値を保存します if (value != v) {
                        // データが変更された場合、データを使用する各場所にデータの更新を通知する必要があり、つまりモデルはビュー層に通知し、Watcherクラスは中間層として動作して操作を完了します(通知操作)
                        値 = v;
                        binding._directives.forEach(関数(項目) {
                            アイテムを更新します。
                        })
                    }
                }
            })
        }
    })
}


// この機能は、カスタム命令をコンパイルし、それらにネイティブ リスニング イベントを追加し、ビューからモデル レイヤーへのデータ通信を実現します。つまり、ビュー レイヤー データが変更されたときにモデル レイヤー データの更新を通知します // 実装の原則: 管理対象要素オブジェクト this.$el を介してすべての子ノードを取得し、すべての子ノードをトラバースして、カスタム属性があるかどうか、および指定された意味を持つカスタム属性があるかどうかを確認します // たとえば、e-bind/e-model/e-click は、ノードに追加されたさまざまなカスタム属性に応じてリスニング イベントを追加します // e-click はネイティブ onclick イベントを追加します。ここでの重要な点は、this.$method で指定されたメソッドのコンテキスト this を this.$data に変更する必要があることです
// e-model はデータ更新にバインドされており、ここでは input タグと textarea タグのみがサポートされています。理由: ビューからモデル レイヤーへのデータ通信は、タグ自体の value 属性を使用して実現されます // e-bind
EBind.prototype._compile = 関数 (ルート) {
    // 実行コンテキストを保存します var _this = this;

    // 管理対象ノード要素のすべての子ノードを取得します(要素ノードのみを含む)。var nodes = root.children;

    (i = 0 とします; i < nodes.length; i++) {
        // 子ノードを順番に取得します var node = nodes[i];

        // 現在のノードに子ノードがある場合は、子ノードをレイヤーごとに処理し続けます。if (node.children.length) {
            this._compile(ノード);
        }

        // 現在のノードが e-click 属性にバインドされている場合は、onclick イベントを現在のノードにバインドする必要があります if (node.hasAttribute('e-click')) {
            // hasAttribute はカスタム属性を取得できますnode.addEventListener('click',(function () {
                // 現在のノードの属性値を取得します。つまり、メソッド var attrVal = node.getAttribute('e-click');
                // バインドされたメソッドのデータは data のデータを使用する必要があるため、実行される関数のコンテキスト、つまり this を this.$data に変更する必要があります。
                // call/apply の代わりに bind を使用する理由は、onclick メソッドをすぐに実行するのではなく、実行前にトリガーする必要があるためです。 return _this.$methods[attrVal].bind(_this.$data);
            })())
        }


        // 双方向バインディングは input および textarea タグ要素にのみ適用できます。これは、これらの 2 つのタグの組み込み value 属性が双方向バインディングの実装に使用されるためです。if (node.hasAttribute('e-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
            // 要素オブジェクトに入力イベントリスナーを追加します。2 番目のパラメータはすぐに実行される関数です。ノードのインデックス値を取得し、関数内のコードを実行して、イベントハンドラーを返します。node.addEventListener('input', (function (index) {
                // 現在のノードの属性値を取得します。つまり、メソッド var attrVal = node.getAttribute('e-model');

                // 現在の要素のビューレイヤーにモデルのマッピングを追加します。object_this._binding[attrVal]._directives.push(new Watcher({
                    名前: '入力',
                    el: ノード、
                    eb: _これ、
                    exp: 属性値、
                    属性: '値'
                }))

                // 入力タグの値が変わった場合は、モデル層のデータを更新する必要があります。つまり、ビュー層からモデル層への変更です。 return function () {
                    // . を区切り文字として使用して、バインドされた属性を取得します。値のみの場合は、現在の値を直接取得します。オブジェクト (obj.key) の形式の場合、バインドは実際には obj オブジェクト内のキーの値です。この時点で、キーを取得して、変更された入力タグの値に割り当てる必要があります。var keys = attrVal.split('.');

                    // 前のステップで取得した属性セットの最後の属性を取得します (最後の属性は実際のバインドされた値です)
                    var lastKey = keys[keys.length - 1];

                    // 実際にバインドされている値の親オブジェクトを取得します // obj.key.val などのオブジェクトの場合、ここで変更したいのは val なので、key の参照を見つける必要があります。
                    // val の値は key を参照して変更しますが、val への参照を直接取得すると、val は数値ストレージになります。これを別の変数に割り当てると、実際には新しいスペースが開きます。 // モデル レイヤーのデータ、つまり this.$data を直接変更することはできません。データ ストレージを参照して別の変数に割り当てると、他の変数の変更が元の参照データに影響します。 // そのため、ここでは、実際のバインドされた値の親オブジェクト、つまり obj.key の obj 値を見つける必要があります。var model = keys.reduce(function (value, key) {
                        // オブジェクトでない場合は、属性値を直接返します
                        if (typeof value[key] !== 'object') {
                            戻り値;
                        }

                        戻り値[キー];
                        // ここでは、モデル レイヤーを開始値として使用します。これは、キーが this.$data 内の属性を記録するため、親オブジェクト this.$data からターゲット属性を見つける必要があるためです。}, _this.$data);

                    // model は前述の親オブジェクト、obj.key 内の obj、lastkey は実際のバインドされた属性です。見つかったら、ノードの値に更新する必要があります。
                    // ここでのモデル レイヤーの変更により、_observe のアクセサー プロパティ セッターがトリガーされるため、このプロパティが他の場所で使用される場合は、それに応じて変更されます。 model[lastKey] = nodes[index].value;
                }
            })(私))
        }


        // e-bind をノードにバインドし、モデルからビューへのマッピングを追加します。これは、e-bind がモデルからビューへのデータ通信を実装し、this._observer では、// definePrototype を通じて実装されているため、ここでは _oberver での実装を容易にするために通信を追加するだけでよいためです。
        if(node.hasAttribute('e-bind')) {
            var attrVal = node.getAttribute('e-bind');
            _this._binding[attrVal]._directives.push(新しいウォッチャー({
                名前: 'テキスト',
                el: ノード、
                eb: _これ、
                exp: 属性値、
                属性: 'innerHTML'
            }))
        }
    }
}

/**
 * オプションプロパティ:
 * 名前: ノード名: テキスト ノード: テキスト、入力ボックス: 入力
 * el: 命令に対応する DOM 要素* eb: 命令に対応する EBind インスタンス* exp: 命令に対応する値: e-bind="test"; test は命令に対応する値* attr: バインドされた属性値。たとえば、e-bind によってバインドされた属性は実際に innerHTML に反映され、v-model によってバインドされたタグは値に反映されます*/
関数ウォッチャー(オプション) {
    this.$options = オプション;
    これを更新します。
}

Watcher.prototype.update = 関数 () {
    // コンテキストを保存 var _this = this;
    // バインドされたオブジェクトを取得します。var keys = this.$options.exp.split('.');

    // DOMオブジェクト上の変更する属性を取得して変更します this.$options.el[this.$options.attr] = keys.reduce(function (value, key) {
        戻り値[キー];
    }, _this.$options.eb.$data)
}

結果:

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • 非常に詳細な基本的なJavaScript構文ルール
  • js 基本構文と Maven プロジェクト構成チュートリアル ケース
  • Javascript における分割代入構文の詳細な説明
  • JSでよく使われるデータ処理方法
  • jsはウォーターフォールフローのボトムアウトによるデータの動的ロードを実現します
  • JavaScriptの基本構文とデータ型の詳細な説明

<<:  Alibaba CloudがCloud Shieldから無料のSSL証明書(https)を申請

>>:  MySQL の複数テーブル関連付け 1 対多クエリを使用して最新のデータを取得する方法の例

推薦する

ウェブサイトにダークモード切り替え機能を持たせるための純粋なCSSフリー実装コード

序文ダーク モードの概念は、 MacOS系統のMojaveに由来し、ユーザーが選択できる 2 つのス...

mysql IS NULL インデックスケースの説明を使用する

導入MySQL の SQL クエリ ステートメントで is null、is not null、!= ...

XMLとCSSスタイルの組み合わせ

学生.xml <?xml バージョン="1.0" エンコーディング=&qu...

Webデザインチュートリアル(1):手順と全体レイアウト

<br />注:ウェブサイトの種類を示すものを除くすべてのテキストは、企業サイト用です。...

Linux の ufw ファイアウォールの紹介

Linux のufw (Uncomplicated Firewall) を見て、ファイアウォールに変...

Docker環境にJenkinsコンテナをインストールする詳細なチュートリアル

推奨される Docker 学習教材: https://www.runoob.com/docker/d...

HTML+CSS+JSはナビゲーションバーのスクロールグラデーション効果を実現します

目次まず効果を見てみましょう:成し遂げる:要約:まず効果を見てみましょう: 成し遂げる: 1. ナビ...

ウェブページ作成のヒントのまとめ

序文この記事は主に、日常の Web ページ制作で遭遇する問題解決スキルの一部をまとめ、皆さんの参考と...

JS 関数とコンストラクタを簡単に理解する

目次1. 概要1.1 Functionコンストラクタを使用して関数を作成する1.2 機能と目的2. ...

vue-tableは追加と削除を実装します

この記事では、vue-table の追加と削除の具体的なコードを参考までに紹介します。具体的な内容は...

Linux システムに Zookeeper サービスをインストールする方法

1. /usr/local/services/zookeeper フォルダを作成します。 mkdir...

Vue3はCSSの無限シームレススクロール効果を実装します

この記事では、CSS無限シームレススクロール効果を実現するためのvue3の具体的なコードを参考までに...

CSS3でカルーセル画像を作成する方法

スライドショーは Web ページでよく見られます。美しい写真が使われています。こちらは純粋な CSS...

Linux で Docker を使用して MySQL をインストールする手順

テスターとして、学習プロセス中に Linux でソフトウェアをインストールする必要が頻繁にある場合が...

ウェブフロントエンド開発者が知っておくべき 9 つの実用的な CSS プロパティ

1. 角を丸くする今日の Web デザインは、常に最新の開発テクノロジーに追随しており、HTML5 ...