JSのプロキシ:何が地獄?


ProxyはJavaで一般的に使用されるものです.例えば、アスペクト指向プログラミングで春にします@Transactional , @Security , ....). しかしJavaScriptでは、それは通常使用されるものではありません.かどうか?
この記事では、
  • 原理
  • API
  • 我々ができることのいくつかの例
  • パフォーマンス
  • どのライブラリを使うか
  • 原理


    アイデアは、オリジナルオブジェクトをラップする新しいオブジェクトを作成し、プロパティを取得/設定するなどの基本的な操作を遮断し、再定義することです.
    より正確に、我々は目標オブジェクトをobject , function , ...). そして、トラップを含んでいるハンドラという名前のオブジェクトに対するインタセプト/再実装をする操作を定義しますget , set , ...).
    以下にこれを再開するイメージを示します.

    API


    今ではJSでプロキシを実装する方法を見る時間です.APIは単純です
    const proxy = new Proxy(target, handler);
    
    The handler を含むオブジェクトtraps インタセプトする
    const handler = {
      get(target, prop, receiver) {
        console.log("Getting the property", prop);
        return target[prop];
      },
      set(target, prop, value) {
        console.log("Setting the property", prop);
        target[prop] = value;
      },
    };
    

    Note: You can also use the Reflect API to implement the proxy.


    取消し可能代理人
    また、与えられた関数のおかげで取り消されることができるプロキシを作成することも可能です.一度破棄されたプロキシは使用できなくなります.
    // `revoke` is the function to revoke the proxy
    const { proxy, revoke } = Proxy.revocable(target, handler);
    

    Note: It's not able to restore the Proxy once revoked.


    そして、我々は行きます.あなたはAPIを知っています、我々が実装することができる若干の例を見ましょう.

    トラップの例


    ゲット


    では始めましょうget トラップ.APIは以下の通りです.
    get(target, property, receiver)
    
    この例では、ユーザーがオブジェクトに存在しないプロパティにアクセスしようとするとエラーをスローするプロキシを行います.
    const person = {
      firstName: "Bob",
      lastName: "TheSponge",
    };
    
    const personProxy = new Proxy(person, {
      get(target, prop) {
        if (!prop in target) {
          throw new Error(
            `The property ${prop} does not exist in the object`
          );
        }
    
        return target[prop];
      },
    });
    
    // Will print: 'Bob'
    console.log(personProxy.firstName);
    
    // Will throw an error
    console.log(personProxy.unknownProp);
    

    適用


    The apply トラップは次のようなものです.
    apply(target, thisArg, argumentsList)
    
    それを説明するために、我々はwithTimerProxy コールバック実行の持続時間を測定します.
    function aFunction(param) {
      console.log("The function has been called with", param);
    
      return "Hello " + param;
    }
    
    function withTimerProxy(callback) {
      return new Proxy(callback, {
        apply(target, thisArg, argumentsList) {
          console.time("Duration");
    
          const result = callback.apply(thisArg, argumentsList);
    
          console.timeEnd("Duration");
    
          return result;
        },
      });
    }
    
    const aFunctionWithTimer = withTimerProxy(aFunction);
    
    // Will print:
    // The function has been called with World
    // Duration: 0.114013671875 ms
    // 'Hello World'
    console.log(aFunctionWithTimer("World"));
    

    Note: If you want to see more console API you can read my article .

    Note: To do such a functionality, we would probably use an High Order Function instead.


    その他トラップ


    以下に使用するトラップの一覧を示します.
  • construct(target, argumentsList, newTarget)
  • defineProperty(target, property, descriptor)
  • deleteProperty(target, property)
  • getOwnPropertyDescriptor(target, prop)
  • getPrototypeOf(target)
  • has(target, prop)
  • isExtensible(target)
  • ownKeys(target)
  • preventExtensions(target)
  • set(target, property, value, receiver)
  • setPrototypeOf(target, prototype)
  • パフォーマンス?


    最近、私はreact-hook-form その法案は使用しないことを決めたProxy パフォーマンスの理由のため、フォームの状態を見る人の追跡のためにもう.
    演奏はそんなに悪いですか.単純なプロパティの値を取得する際のパフォーマンスコストを測定してみましょう.
    私はbenchmark 図書館.以下にスクリプトを実行します.
    const Benchmark = require("benchmark");
    
    const suite = new Benchmark.Suite();
    
    const person = {
      firstName: "Bob",
      lastName: "TheSponge",
    };
    
    const personProxy = new Proxy(person, {});
    
    suite
      .add("native", () => {
        person.firstName;
      })
      .add("proxy", () => {
        personProxy.firstName;
      })
      .on("cycle", (event) =>
        console.log(event.target.toString())
      )
      .run({ async: true });
    
    結果は次のようになります.

    もちろん、ネイティブの実装は、プロパティにアクセスするだけで高速です.プロキシ実装はネイティブのものよりもずっと遅いです.しかし、それはそんなに悪くないと思います.
    あなたがインターネットについて検索する場合は、プロキシのパフォーマンス、一部の人々はそれが開発のためのツールであり、生産に使用すべきではないと言う.個人的には、あなたのユースケース、プロキシを使って処理したいデータ量、必要なパフォーマンスに依存していると思います.あなたは、概念(POC)の証明でそれをテストすることができます.
    プロキシに依存するライブラリがあり、これは生産で使用できることを証明します.それらの2つを見ましょう.

    Note: It's good to note that the "selling point" of react-hook-form is the performance, so it makes sense not to use Proxy.


    実使用事例


    ソルダージャ


    SolidJSは、UIを構築するための宣言的なライブラリです.これは仮想DOM(反応に反して)を使用しません.
    コードを書く方法は、以下のようになります.
  • 日本学術振興会
  • コンポーネント
  • state =>シグナル
  • 使用します
  • ユーズモス=クレタネム
  • ...
  • しかし、フック規則はありません、あなたの小道具を分解してはいけません、すべての構成要素はそれを実行します、そして、使用された反応原始が変化したとき、それは副作用を実行します.
    これは、反応還元剤と同等のストアのプロキシを使用します.
    あなたがsolidjsを知らない場合は、それは有望な未来を持ってチェックしてください.
    例えば、ここでは簡単ですCounter コンポーネント
    import { createSignal } from 'solid-js';
    
    function Counter() {
      const [count, setCount] = createSignal(0);
      const increment = () => setCount(count() + 1);
    
      return (
        <button type="button" onClick={increment}>
          {count()}
        </button>
      );
    }
    

    Note: This is not an exhaustive list of similarities / differences and how to use the library. When I feel more confident, I will try to write an article about it because I think it has a bright future ahead of it.


    イマジン


    Minimjsでは、不変のデータ構造を作成することができますが、変更可能な方法でデータを変更する可能性を与えてくれます.
    例えば、次のようにします.
    import product from "immer";
    
    const person = {
      firstName: "Bob",
      lastName: "TheSponge",
    };
    
    const newPerson = produce(person, (p) => {
      p.firstName = "Patrick";
      p.lastName = "Star";
    });
    
    // Will return false
    console.log(newPerson === person);
    
    これは、オブジェクトを変異せずに変更を簡素化する便利な方法であり、多くのコピーを行うことなく.
    const person = {
      firstName: "Bob",
      lastName: "TheSponge",
      address: {
        type: "pineapple",
        city: "Bikini bottom",
      },
    };
    
    // It prevents us to make things like
    const newPerson = {
      ...person,
      address: {
        ...person.address,
        // Note the uppercase
        type: "Pineapple",
      },
    };
    

    Note: Redux Toolkit uses it under the hood to make reducers easier to implement.


    結論


    プロキシは、多くの機能を有効にすると、それらのおかげで再利用可能ないくつかのロジックを抽出することができます.いくつかのライブラリを使用してコードの実行を最適化します.例えば react-tracked and proxy-memoize その利用 proxy-compare フードの下で、あなたの再レンダリングを減らします.これらのライブラリは、開発したダイシによって開発されますuse-context-selector 私が記事に記載した図書館.
    しかし、私はあなたがそれが必要であるとき、ユニークに彼らを使用するよう勧めます.
    があるTC39 proposal JavaScriptにネイティブのデコレータを追加するには、いくつかの他の可能性を有効にする見込みがあります.たとえば、機能のログを測定/測定するには、.簡単な方法で@Logger or @Performance (これらの注釈の正しい実装を行う).
    コメントすることを躊躇しないでください、そして、あなたがより多くを見たいならば、あなたは私に続くことができるか、私のところに行くことができますWebsite .