JavaScript設計モード(三):プロキシモード


プロキシモード:オブジェクトに代替品またはプレースホルダを提供して、アクセスを制御します.
私たちはあるオブジェクトに直接アクセスするのが不便な場合や、必要を満たしていない場合は、身代わりオブジェクトを使ってそのオブジェクトのアクセスを制御することが考えられます.身代わりの対象は要求を先に処理してから、本体に渡すかどうかを決めることができます.
生活栗:
  • 代理購入
  • スターマネージャー;
  • 協調インターネット
  • よくshopingのクラスメートは、代理購入に慣れていないはずです.自分で直接買うのは不便です.ある商品が買えない場合は、第三者に委託して、代理購入やダフ屋に購買動作をさせます.プログラムの世界の代理人もそうです.私たちは直接に元の対象を操作しないで、代行者に依頼します.代理人の役割は、私たちの要求を事前に処理したり、実際のオブジェクトに転送したりすることです.
    パターンの特徴
  • エージェントは、要求を予め処理してから、本体に渡すかどうかを決定することができる.
  • エージェントと本体の外部ディスプレイインターフェースは一貫性を保つ
  • .
  • プロキシオブジェクトは、本体だけを一回包装する
  • です.
    パターンの細分化
  • 仮想エージェント(オーバヘッドが大きい演算を必要に応じて実行する)
  • キャッシュエージェント(オーバヘッドが大きい演算結果にキャッシュを提供する)
  • 保護エージェント(白黒オーボエ、エージェントはブラックフェースとして働き、非差別的要求をブロックする)
  • ファイアウォールエージェント(ネットワークリソースのアクセスを制御する)
  • リモートエージェント(異なるアドレスコントロールにおけるオブジェクトのローカル表現を提供する)
  • インテリジェント引用エージェント(訪問先はいくつかの追加動作を実行する)
  • 書き込み時にコピープロキシ(遅延対象コピープロシージャ、オブジェクトが本当に修正される必要がある場合のみ実行)
  • JavaScriptでよく使われるプロキシモードは、「仮想エージェント」と「キャッシュエージェント」である.
    パターン実現
    インプリメンテーション:プロキシオブジェクトを作成し、プロキシオブジェクトは要求を処理してから、本体に渡すかどうかを決定し、プロキシと本体の対外インターフェースは一貫性を維持する(インターフェース名は同じ).
    //   :      ,       
    var backPhoneList = ['189XXXXX140'];       //      
    //   
    var ProxyAcceptPhone = function(phone) {
        //    
        console.log('      ...');
        if (backPhoneList.includes(phone)) {
            //   
            console.log('       ');
        } else {
            //   
            AcceptPhone.call(this, phone);
        }
    }
    //   
    var AcceptPhone = function(phone) {
        console.log('    :', phone);
    };
    
    //       
    ProxyAcceptPhone('189XXXXX140'); 
    ProxyAcceptPhone('189XXXXX141'); 
    代理は本体の対象を変えることはなく、「単一職責原則」、すなわち「自分で家の前の雪を掃き、それぞれの家を探す」という原則を遵守します.異なるオブジェクトは独立した役割を担っており、あまり緊密に結合しすぎず、具体的な実行機能はまだ本体オブジェクトであり、導入エージェントだけは要求を選択的に前処理することができる.例えば上記のコードにおいて、「電話を受ける機能」本体にブラックリストをブロックする機能(保護エージェント)を追加し、予め電話アクセス要求を処理しておきます.
    仮想エージェント(遅延実行)
    仮想エージェントの目的は、オーバーヘッドが大きい演算を必要に応じて遅延させて実行することである.
    仮想プロキシの画像プリロードのアプリケーションは、コードの例は「JavaScript設計モードと開発実践」になります.
    //   
    var myImage = (function(){
        var imgNode = document.createElement('img');
        document.body.appendChild(imgNode);
        return {
            setSrc: function(src) {
                imgNode.src = src;
            }
        }
    })();
    
    //   
    var proxyImage = (function(){
        var img = new Image;
        img.onload = function() {
            myImage.setSrc(this.src);             //            src
        }
        return {
            setSrc: function(src) {
                myImage.setSrc('./loading.gif');  //       src loading 
                img.src = src;
            }
        }
    })();
    
    //     
    proxyImage.setSrc('./product.png');           //  loading         
    キャッシュエージェント(一時記憶)
    キャッシュエージェントの目的は、いくつかのオーバーヘッドの大きな演算結果の一時的な記憶を提供し、次回の起動時にパラメータと結果が変わらない場合、キャッシュから結果を返します.
    キャッシュエージェントの本体を適用して、要求される演算関数は純粋な関数であるべきであり、例えば、求和関数sum、入力パラメータ(1, 1)、結果は常に2であるべきである.
    純関数:固定入力で、固定出力があり、外部データに影響しません.
    シミュレーションシーン:60の判定問題テスト、3つの問題ごとに1回採点して、スコアによって次のステップの3つのテーマを選別しますか?
    三つの判定問題の得点結果:
  • (0,0,0)
  • (0,0,1)
  • (0,1,0)
  • (0,1,1)
  • (1,0,0)
  • (1,0,1)
  • (1,1,0)
  • (1,1,1)
  • 全部で七種類のスコアの結果です.60/3 = 20、合計20回のスコアを行い、毎回スコアを計算して3サイクルの累計を行い、合計60サイクルです.次に、「キャッシュエージェント」方式を用いて、最小本体演算回数を実現する.
    //   :          
    var countScore = function(ansList) {
        let [a, b, c] = ansList;
        return a + b + c;
    }
    
    //   :         
    var proxyCountScore = (function() {
        var existScore = {};    //       
        return function(ansList) {
            var attr = ansList.join(',');  // eg. ['0,0,0']
            if (existScore[attr] != null) {
                //      
                return existScore[attr];
            } else {
                //      ,           
                return existScore[attr] = countScore(ansList);
            }
        }
    })();
    
    //     
    proxyCountScore([0,1,0]);
    60のテーマは、3つの問題ごとに1回の点数を計算して、合計20回の点数計算を行いますが、合計スコアの結果は7種類しかありません.実際には、本体countScore()は最大7回まで計算すれば、すべての計算結果を網羅することができます.
    キャッシュエージェントのようにスコア結果を一時的に記憶する.答え文字列で属性名['0,1,0']を構成してkey値としてメモリを検索し、メモリから直接返すと複雑な演算を含む本体が呼び出される回数を減らす.その後、もし私たちのテーマが90コースに追加されたら、120コース、150問の場合、本体countScore()の演算回数は依然として7回維持され、中間で複雑な演算の出費が節約されます.
    ES 6のProxy
    ES 6に追加されたProxyプロキシオブジェクトの動作は、具体的な実施形態は、handler上でオブジェクトカスタム方法のセットを定義して、予めオブジェクトの動作を制御する.
    ES 6のProxy文法:let proxyObj=new Proxy(target、handler);
  • target:本体、代行対象
  • handler:カスタム操作方法セット
  • proxyObj:戻るプロキシオブジェクトは、本体の方法がありますが、handlerによって前処理されます.
    // ES6 Proxy
    let Person = {
        name: '    '
    };
    
    const ProxyPerson = new Proxy(Person, {
        get(target, key, value) {
            if (key != 'age') {
                return target[key];
            } else {
                return '  '
            }
        },
        set(target, key, value) {
            if (key === 'rate') {
                target[key] = value === 'A' ? '  ' : '   '
            }
        }
    })
    
    console.log(ProxyPerson.name);  // '    '
    console.log(ProxyPerson.age);   // '  '
    ProxyPerson.rate = 'A';         
    console.log(ProxyPerson.rate);  // '  '
    ProxyPerson.rate = 'B';         
    console.log(ProxyPerson.rate);  // '   '
    handlerは、一般的なset/getを除いて、全部で13の方法をサポートする.
    handler.getPrototypeOf()
    //                 ,      Object.getPrototypeOf(proxy)  
    
    handler.setPrototypeOf()
    //                 ,      Object.setPrototypeOf(proxy, null)  
    
    handler.isExtensible()
    //                      ,      Object.isExtensible(proxy)  
    
    handler.preventExtensions()
    //                   ,      Object.preventExtensions(proxy)  
    
    handler.getOwnPropertyDescriptor()
    //                       ,      Object.getOwnPropertyDescriptor(proxy, "foo")  
    
    handler.defineProperty()
    //                        ,      Object.defineProperty(proxy, "foo", {})  
    
    handler.has()
    //                      ,      "foo" in proxy  
    
    handler.get()
    //                   ,      proxy.foo  
    
    handler.set()
    //                    ,      proxy.foo = 1  
    
    handler.deleteProperty()
    //                   ,      delete proxy.foo  
    
    handler.ownKeys()
    //                    ,      Object.getOwnPropertyNames(proxy)  
    
    handler.apply()
    //                        ,      proxy()  。
    
    handler.construct()
    //                             ,      new proxy()  
    適用シーン
  • 仮想エージェント:
  • ピクチャプリロード(loading図)
  • HTTP要求(データ上報まとめ)を統合する
  • キャッシュエージェント:(前提本体は純関数)
  • は、非同期要求データ
  • をキャッシュする.
  • は、より複雑な演算結果をキャッシュする
  • .
  • ES 6のProxy:
  • は、オブジェクトのプライベート属性
  • を実現する.
  • フォーム検証を実施する
  • 「ポリシーモード」は、フォーム検証情報に適用されてもよく、「エージェント方式」は実装されてもよい.ここではGithub-jawilの例を引用して、アイデアを共有します.
    //    proxy         
    function validator(target, validator, errorMsg) {
        return new Proxy(target, {
            _validator: validator,
            set(target, key, value, proxy) {
                let errMsg = errorMsg;
                if (value == null || !value.length) {
                    console.log(`${errMsg[key]}     `);
                    return target[key] = false;
                }
                let va = this._validator[key];  //           
                if (!!va(value)) {
                    return Reflect.set(target, key, value, proxy);
                } else {
                    console.log(`${errMsg[key]}      `);
                    return target[key] = false;
                }
            }
        })
    }
    
    //          
    const validators = {
        name(value) {
            return value.length >= 6;
        },
        passwd(value) {
            return value.length >= 6;
        },
        moblie(value) {
            return /^1(3|5|7|8|9)[0-9]{9}$/.test(value);
        },
        email(value) {
            return /^\w+([+-.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(value)
        }
    }
    
    //     
    const errorMsg = {
        name: '   ',
        passwd: '  ',
        moblie: '    ',
        email: '    '
    }
    const vali = validator({}, validators, errorMsg)
    let registerForm = document.querySelector('#registerForm')
    registerForm.addEventListener('submit', function () {
        let validatorNext = function* () {
            yield vali.name = registerForm.userName.value
            yield vali.passwd = registerForm.passWord.value
            yield vali.moblie = registerForm.phone.value
            yield vali.email = registerForm.email.value
        }
        let validator = validatorNext();
        for (let field of validator) {
            validator.next();
        }
    }
    構想を実現する:ES 6のproxyを利用してhandlerset()をカスタマイズし、フォーム検証を行い、結果を返し、「ポリシーモード」を利用して検証ロジックを実行する.
    フォーム検証での「デザインモード」の応用については、Jawil原文を参照してください.
    長所と短所
  • 長所:
  • は、外部からの本体オブジェクトへのアクセスを傍受し、傍受することができる.
  • 複雑な演算の前に検証またはリソース管理ができます.
  • 対象機能の粒度が細分化され、機能の複雑さが減少し、「単一職責原則」に適合する.
  • は、代理により、本体オブジェクトを変更せずに拡張機能を追加することができ、「開発・閉鎖の原則」
  • に適合する.
  • 欠点:
  • 追加代理オブジェクトの作成により、メモリオーバーヘッドの一部が追加されました.
  • 処理要求速度は異なるかもしれないが、直接アクセスではなくオーバーヘッドが存在するが、「仮想エージェント」および「キャッシュエージェント」は、性能を向上させることができる
  • .
    参考文献
  • 「JavaScript設計モードと開発実践」
  • 『ES 6のプロキシモード-----Proxy』
  • 「2つの優雅なフォーム検証を探索する――ポリシー設計モードとES 6のProxyエージェントモード」
  • Github、スターを期待します!https://github.com/ZengLingYong/blog
    作者:以楽之名
    この文章はオリジナルで、不適切なところがあります.転載は出典を教えてください.