ECMAScript-2015プロキシを使用した一方向データバインディング





こんにちは、ハブラフチャン。 今日は、プロキシとその他のECMAScript 2015の便利な機能を使用して、一方向のデータバインディング機能を備えたデータウェアハウスを作成します。



プロキシとは何ですか?



簡単に言えば、プロキシはオブジェクトラッパーであり 、これを使用すると、作成元のオブジェクトへの呼び出しをインターセプトできます。 呼び出しをインターセプトするために、トラップの武器で武装したプロキシには、いくつかのインターセプター機能があります。 フックリストとすべてのプロキシメソッドの詳細については、 こちらをご覧ください



どうする?



プロキシを使用して、変更追跡機能を備えたオブジェクトのリポジトリを実装します。 ある種の故人のウーといくつかの追加の特典。



さあ行こう...



仕事用



ストレージは、ストレージファクトリクラスのインスタンスです。



"use strict"; class OS { //    } window.ObserveStorage = new OS();
      
      





将来発生するすべては、OSクラス内で発生します。



リポジトリには、次のデータ構造が必要です。



 {  : {  :    }  :{  :{   :{ id :  } } } }
      
      





したがって、必要なすべての機能を実装するために、コンストラクターでデータストレージのオブジェクトを定義します。



 class OS { constructor() { this.storage = new Map(); //   this.listeners = new Map(); //   this.serviceField = new Set([`on`, 'un']); //”” , ..    . } }
      
      





MapおよびSetクラスの説明は意図的に省略します。 それらについて詳しく知りたい場合は、 こちらこちらをご覧ください



serviceFieldフィールド 、オーバーヘッドを上書きまたは反復する可能性を排除するために必要ですが、それについては後で詳しく説明します。



次のステップは、新しいオブジェクトをリポジトリに追加する組織です。



オブジェクトを持つ:



 let object = {key1: ”data”, key2: 1}
      
      





オブジェクトをストアに追加する次のメソッドを実装します。



 let wrapper = ObserveStorage.add(key, object); //return Proxy
      
      







最初のパラメータは、オブジェクトがリポジトリに記録されるキーを決定し、2番目はオブジェクト自体です。 出力では、ベースオブジェクトのプロキシラッパーを取得します。



事前に、すべてのユーザーがリポジトリからオブジェクトを取得する機能に関心があるわけではなく、すべてのキーを監視することは必ずしも便利ではないため、このエントリも有効です。



 let wrapper = ObserveStorage.add(object);
      
      





異なるキーとIDを使用するため、それらを生成する簡単なメソッドを作成します。



 static __getId() { return (`${Math.random().toFixed(10).toString().replace("0.", "")}${Date.now()}`) }
      
      





インターフェイスを定義し、さまざまな種類の識別子を生成するツールが用意できたので、メソッドの開発を開始できます。



 add(...arg) { //    1  2  let key, object; if(arg.length == 1){ [object] = arg; key = OS.__getId(); //  id } else [key, object] = arg; //       ,  ,    ,  . //      ,     . // 1)     : if (this.storage.has(key)) { throw new Error(`key ${key} is already in use`); } // 2)     () ,     ,     (:  ,    ,      –      ): let self = this; object.on = (...arg)=> self.on(key, ...arg); //  object.un = (...arg)=> self.un(key, ...arg); //  //        storage const proxy = this.getProxy(key, object); //return Proxy //    Map  this.listeners.set(key, new Map()); //, ,     this.storage.set(key, proxy); // ,     ,     return proxy; }
      
      





getProxyメソッドは未解決のままでしたが、私はあなたのことは知りませんが、秘密は我慢できません。 したがって、行こう:



 // getProxy  2 , 1 –  ,    ,  2 –   . getProxy(key, object){ let self = this; //    ,      return new Proxy(object, { //       get(target, field) { return target[field]; }, //       set(target, field, value) { // ,       if(self.serviceField.has(field)) throw new Error(`Field ${field} is blocked for DB object`); const oldValue = target[field]; target[field] = value; //       fire  OS. self.fire(key, { type: oldValue ? "change" : "add", property: field, oldValue: oldValue, value: value, object: self.get(key) }); //       , ,           Oo,    -  . return true }, //     deleteProperty(target, field) { //       if (!field in target || self.serviceField.has(field)) { return false; } const oldValue = target[field]; delete target[field]; self.fire(key, { type: "delete", property: field, oldValue: oldValue, value: undefined, object: self.get(key) }); return true; }, //  Object.getOwnPropertyNames() ,         “”      ownKeys(target) { let props = Object.keys(target) .filter(function (prop) { return !(self.serviceField.has(prop)); }); return props; } } ); }
      
      





イベントを生成するときに注意することが重要です



 self.fire(key, { type: oldValue ? "change" : "add", property: field, oldValue: oldValue, value: value, object: self.get(key) });
      
      





ターゲットはtargetではなく、ラッパーです。 これは、 コールバック内のオブジェクトを変更することにより、ユーザーが追跡されていない変更を行わないようにするために必要です。 最初に、オブジェクトのコピーをそこに転送しましたが、実際にはあまり良くありません。 上記のブロックでは、.getや.fiteなどのメソッドが強調表示されているため、順番にそれらについて説明しましょう。



.getメソッドは、リポジトリ内のオブジェクトを単にチェックし、それを返します。



 get(key) { if(this.storage.has(key)) return this.storage.get(key); else{ console.warn(`Element ${key} is not exist`); return undefined; } }
      
      





.fireメソッドについて説明する前に、イベントサブスクリプションについて言及する価値があります。 サブスクリプションには次のインターフェイスが使用されます。



 wrapper.on(callback, property = "*");
      
      





どこで



 property = "*"
      
      





はデフォルト値であり、このオブジェクトのすべてのフィールドへのサブスクリプションを示します。



例:



 wrapper.on(event => console.log(JSON.stringify(event)), "value"); wrapper.data = "test"; //   wrapper.value = 2; // Object{"type":"change","property":"value","oldValue":4,"value":2,"object":{"data":"test","value":2}} wrapper.on(event => console.log(JSON.stringify(event)), "*"); wrapper.data = "test"; // Object{"type":"change","property":"data","oldValue":”text”,"value":”test”,"object":{"data":"test","value":1}}
      
      





リポジトリにオブジェクトを記録するときに、このメソッドをオブジェクトに統合します(上記を参照)。 メソッド自体は次の関数です。



 on(key, callback, property = "*") { //   callback   if (!key || !callback) { throw new Error("Key or callback is empty or not exist"); } // Map     const listeners = this.listeners.get(key), //  id    subscriptionId = OS.__getId(); //  ,     ,    ,      Map !listeners.has(property) && listeners.set(property, new Map()); //        id listeners .get(property) .set(subscriptionId, callback); //  id     return subscriptionId; }
      
      





.onメソッドの最初のパラメーターに特に注意を払います。 注意深いのは、1つまたは2つのパラメーターが渡されることですが、メソッドは3つを期待し、そのうちの1つがキーであることに気付きました。



そして、最も注意深い人は、リポジトリ、つまり次の行でオブジェクトを初期化するときにメソッドのキーをロックしたことを覚えています。



 object.on = (...arg)=> self.on(key, ...arg);
      
      





サブスクリプションを解除するには、サブスクリプション中に受け取ったサブスクリプションIDを使用する必要があります。



 wrapper.un(subscriptionId);
      
      





機能説明:



 un(key, subscriptionId) { //    ,   if (!key) { throw new Error("Key is empty or not exist"); } //     const listeners = this.listeners.get(key); if (listeners) //      Map for (let listener of listeners.values()) { //        if (listener.delete(subscriptionId)) return true; } return false; }
      
      





さまざまな種類の操作にidを使用するのが好きです。これにより、かなり透明な形式でユーザーアクションを明確に識別できるからです。



それで、ラッパーにかかっているすべてのコールバックをプルする.fireメソッドを呼び出すことになりました。



 fire(key, event) { //   let listeners = this.listeners.get(key) ,property = event.property; //    listeners.has(property) && this.fireListeners(event, listeners.get(property)); //    listeners.has("*") && this.fireListeners(event, listeners.get("*")); }
      
      





fireListenersメソッドは透過的であり、説明は不要です。



 fireListeners(event, listeners) { listeners.forEach((listener)=> { setTimeout(()=> listener(event), 0); }) }
      
      





まとめると



したがって、オブジェクトの変更をサブスクライブする機会を得ながら、わずか150行のコードでデータウェアハウスを記述しました。 Ooの遺産に従い、現在、ネストされたオブジェクトをラップせず、配列を処理しませんが、必要に応じてこれらすべてを実装できます。



完全なコードはこちらにあります



あなたと一緒に、 罪人

あなたに良い、地球人。



All Articles