Proxy in JS: what the hell?

Proxy is something that is commonly used in Java, for example in Spring with Aspect-Orientied Programming (@Transactional, @Security, ….). But in Javascript, it’s not something that it is usually used. Or is it? 🙂

In this article, you are going to …


This content originally appeared on DEV Community and was authored by Romain Trotard

Proxy is something that is commonly used in Java, for example in Spring with Aspect-Orientied Programming (@Transactional, @Security, ....). But in Javascript, it's not something that it is usually used. Or is it? :)

In this article, you are going to see:

  • the principle
  • the API
  • some examples of what we can do
  • performances
  • which libraries use it

Principle

The idea is that we are going to create a new object that wraps the original one, and intercepts and redefines fundamental operations such as getting / seting a property, executing a function , ...

More precisely, we are going to proxify a target object (an object, function, ...). And define the operations that we want to intercept / reimplement thanks to an object named handler that contains traps (for example get, set, ...).

Here is an image to resume this:

Javascript proxy

API

Now it's time to see how to implement Proxy in JS. The API is simple

const proxy = new Proxy(target, handler);

The handler is an object containing all the traps we want to intercept:

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.

Revocable Proxy

It's also possible to create a Proxy that can be revoked thanks to a given function. Once revoked the proxy becomes unusable.

// `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.

And here we go. You know the API, let's see some example we can implement.

Example of trap

Get

Let's start with the get trap. Its API is:

get(target, property, receiver)

For the example we are going to do a proxy that will throw an error if the user tries to access a property that doesn't exist on the object.

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);

Apply

The apply trap is the following one:

apply(target, thisArg, argumentsList)

To illustrate it, we are going to implement a withTimerProxy that will measure the duration of a callback execution.

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 Unknown console API in JS.

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

Other traps

Here is the exhaustive list of trap you can use:

  • 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)

Performances?

Recently, I have seen in the react-hook-form implementation, that Bill decided not to use Proxy anymore for the tracking of who watch the state of the form because of performances reasons.

Are the performances so bad? Let's try to measure the performance cost when retrieving the value of a simple property.

I will use the benchmark library. Here is the script I will run:

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 });

The result is the following one:

Performance result

Of course, the native implementation is faster because it just access the property. The proxy implementation is largely slower than the native one. But I think it's not so bad.

If you search on the internet about, performances of proxy, some people say that it's a tool for development and should not be used in production. Personally, I think it depends on your use case, the amount of data you want to process with Proxy and the performance you want to have. You can test that with a Proof Of Concept (POC).
There are libraries that rely on proxies, which proves that this can be used in production. Let see two of them.

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.

Real use case

SolidJS

SolidJS is a declarative library to build UI, thatrelies on fine grained reactivity. It does not use a virtual DOM (contrary to React).
The way of writing the code is quite similar to React:

  • JSX
  • Component
  • state => signal
  • useEffect => createEffect
  • useMemo => createMemo
  • ...

But there is no hook rules, you should not destructure your props, every components executes ones then it will execute side effect when a used reactive primitive has changed.
It uses Proxy for store which is the equivalent of React reducers.
If you don't know SolidJS, go check it has a promising future.

For example here is a simple Counter component:

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.

ImmerJS

ImmerJS allows us to create immutable datastructures, but giving us the possibility to change its data in a mutable way.

For example, you will able to do:

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);

It's a convenient way to simplify changes without mutates an object, and without to make a lot of copy.

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.

Conclusion

Proxy enables a lot of features, thanks to them you can extract some logic that will be reusable. Some libraries use them to optimize your code execution. For example react-tracked and proxy-memoize that use, proxy-compare under the hood, will reduce your re-render. These libraries are developed by Dai Shi who also made the use-context-selector library that I demystified in the article use-context-selector demystified.
But I would recommend you to use them uniquely when it's necessary.

There is a TC39 proposal to add natively Decorators to javascript, looks promising, which will enable some other possibilities. For example to log / measure performance of function, ... in an easy way: with a @Logger or @Performance (with the right implementation behind these annotations).

Do not hesitate to comment and if you want to see more, you can follow me on Twitter or go to my Website.


This content originally appeared on DEV Community and was authored by Romain Trotard


Print Share Comment Cite Upload Translate Updates
APA

Romain Trotard | Sciencx (2022-04-20T19:59:30+00:00) Proxy in JS: what the hell?. Retrieved from https://www.scien.cx/2022/04/20/proxy-in-js-what-the-hell/

MLA
" » Proxy in JS: what the hell?." Romain Trotard | Sciencx - Wednesday April 20, 2022, https://www.scien.cx/2022/04/20/proxy-in-js-what-the-hell/
HARVARD
Romain Trotard | Sciencx Wednesday April 20, 2022 » Proxy in JS: what the hell?., viewed ,<https://www.scien.cx/2022/04/20/proxy-in-js-what-the-hell/>
VANCOUVER
Romain Trotard | Sciencx - » Proxy in JS: what the hell?. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/04/20/proxy-in-js-what-the-hell/
CHICAGO
" » Proxy in JS: what the hell?." Romain Trotard | Sciencx - Accessed . https://www.scien.cx/2022/04/20/proxy-in-js-what-the-hell/
IEEE
" » Proxy in JS: what the hell?." Romain Trotard | Sciencx [Online]. Available: https://www.scien.cx/2022/04/20/proxy-in-js-what-the-hell/. [Accessed: ]
rf:citation
» Proxy in JS: what the hell? | Romain Trotard | Sciencx | https://www.scien.cx/2022/04/20/proxy-in-js-what-the-hell/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.