JavaScript ES6 - Proxy API

A. Basics:

  1. Proxy API helps in meta programming like Reflect API or Symbols.
  2. Meta-programming means that you’re able to change (parts of) the behaviour of the underlying language — JavaScript in this case. This of course is a powerful feature as it allows you to influence the way your code is executed.
  3. Suppose there is an object with some properties in it. A source code wants to access it

— in regular case directly access

4. A proxy is a wrapper between the object. It has different traps. For example getting the data we have get through traps

— All data in proxy are handled through traps.

5. All the Reflect APIs/ methods which are there can be directly used in traps

Traps and proxy

To understand Javascript Proxy, you need to first know what traps are. Proxies make use of traps for detecting calls or invocation of internal methods of an object, and to intercept these calls, like setting, and getting object properties.

In the above code snippet, the [[SET]] internal method of the object would be invoked by the javascript engine because we are giving assigning a value to our hero object ‘realname’ property. When implemented, Traps would be executed before the internal [[SET]] method is called (before our hero name is set to “black panther”). Traps are triggered once an operation is to be carried on our target object. Implementations of traps are defined in a proxy handler.

B. Traps in Action:

→ Anything inside proxy will happen through traps

→ All the Reflect methods we have can be used in traps in a similar argument fashion. Get() method is one of them.

→ Some of other Reflect API methods that can be used in traps are —

Some of the Reflect API methods are:

Reflect.construct(), Reflect.apply(), Reflect.getPrototypeOf(), Reflect.setPrototypeOf(), Reflect.get(), Reflect..set(), Reflect.has(), Reflect.ownKeys(), Reflect.defineProperty(), Reflect.deleteProperty(), Reflect.preventExtensions(), Reflect.isExtrensible

Reference for Reflect API — JavaScript ES6 Reflect API

→ So we are using object through proxy API

Eg1

let person = {  age: 20}let handler = {
// get args take args similar to Reflect.get(), and so on
get: function(target, name) { return name in target ? target[name] : 'Not defined'; }}let proxy = new Proxy(person, handler);console.log(proxy.age);console.log(proxy.name);

NOTE:

i. Since proxy is a wrapper on object, we can call it like an object. There is one layer of handler.

ii. handler function is called traps, which has set of rules to access object

→ Less code

→ Easier to maintain

→ Flexible

C. Proxies and Reflect:

→ Reflect APIs and Proxy works in sync

→ Let us use set API

→ A single handler has all the methods for proxies

→ We can write some set of rules for each traps

Eg2:

let person = {  age: 20,  name: 'Amir'};let handler = {  get: function(target, name) {    return name in target ? target[name] : 'Not defined';  },  set: function(target, property, value) {    if(value.length >= 2) {      Reflect.set(target, property, value);    }  }}let proxy = new Proxy(person, handler);// proxy.name = 'I';  // this will not update name due to proxy ruleproxy.name = 'John Snow'; // Updates name propertiesconsole.log(proxy.name);                        // John Snow
When accessing length < 2. violating proxy rule, hence will not update name
When validation rules defined in trap passed

Eg2:

let handler = {  get: function(target, name) {    return name in target ? target[name] : 'Not defined';  },  set: function(target, property, value) {    if(property === 'age') {      if(!Number.isInteger(value)) {        throw new TypeError('Age should be integer!');      }      if(value > 150) {        throw new RangeError('Invalid age');      }    }    Reflect.set(target, property, value);  }}let proxy = new Proxy({}, handler);
proxy.age = 'superman'; // throw error due to rules set
console.log(proxy.name);
Passing invalid age
Passing string in age

→ So the cool thing is traps and Reflect map APIs arguments are same which makes interaction easy.

Reference — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

D. Using Proxies as prototype:

Eg.

let person = {  name: 'Amir'};let handler = {  get: function(target, name) {    return name in target ? target[name] : 'Not existing';  }}let proxy = new Proxy({}, handler);Reflect.setPrototypeOf(person, proxy);console.log(person.name);
Proxy validations working — i.e. proxy running in background
Without proxy

E. Proxies can wrap proxies

→ Suppose there is a case when all the object have their proxies. Some master proxy should be applied in limited proxies.

→ Basically adding proxy in proxy. Think this as extra validation on top of validation for some sensitive data.

Eg.

let person1bankDetails = {  bank: 'HDFC',  account: '20233797970'};let person2bankDetails = {  bank: 'ICICI',  account: '122'};let handler = {  get: function(target, name) {    return name in target ? target[name] : 'Not existing';  }}let masterhandler = {  get: function(target, name) {    if(name === 'account') {      return target[name].length >= 11 ? target[name] : 'Invalid acount number'    }    return name in target ? target[name] : 'Invalid'  }}let proxy = new Proxy(person1bankDetails, handler);let masterProxy = new Proxy(proxy, masterhandler);console.log(masterProxy.account);console.log(masterProxy.bank);
Using person1bankDetails obj — correct case
Using person1bankDetails obj — invalid a/c number
Proxies running in background

F. Function as a wrapper:

→ We can wrap pretty much everything in JavaScript, as everything is object.

→ Functions are also object.

→ Reflect.apply() method is used for calling a function

Eg.

// Using function this time instead of obj literal
function log(message) {
console.log('Log entry create, message: ' + message);}let handler = { apply: function (target, thisAttribute, arguments) { arguments.length === 1 ? Reflect.apply(target, thisAttribute, arguments) : null; }};// Adding proxylet proxy = new Proxy(log, handler);proxy("pipeline failing with 500 error message"); // calling a function with proxyproxy("pipeline failing with 500 error message", 100);

G. Revoking a Function:

→ Revoking a function means once the proxy work is done. Close the proxy.

→ So if you do not need that proxy anymore, you can get rid of it and revoke it.

→ This will generate a TypeError if that proxy is used again.

let person = {  name: 'Amir',  age: 28};
let handler = {
get: function(target, property) { return Reflect.get(target, property); }};// Creating Revocable Proxy
let { proxy, revoke } = Proxy.revocable(person, handler);
console.log(proxy.name);revoke();console.log(proxy.name)
Revoking after use
Without revoking — proxy has power to revoke or not based on requirements

→ This was the brief understanding about Proxy APIs. I hope you can try implementing in your projects.

Some Realtime examples:

a. More traps

A proxy can be used for various things, like logging, security, profiling and new design patterns, etc. So let’s say we are building a task project and we want to log to the console, once a task is completed.

b. Securing access with proxy

Like I earlier stated, javascript Proxy API can be used for security purposes. In the code below we created an object that contains a secret and we don’t want this secret to be accessed externally.

The code throws an error whenever we try to access the secret property. The code snippet above should not be used for a project that could go into production, I only did this to show an example. you can also implement the delete trap just like the same way the code implemented the `get` trap.

Limitations of proxies

Not all object usage can be trapped,

  • Object used in comparison can’t be trapped (== and ===)
  • Applying operators on an object or coercing object type can’t be trapped(Number(object), (object +””))
  • Using typeof or instanceof on an object can’t be trapped
  • Some low-level objects like Maps, Set, Date, Promise, Don’t make use of the [[Get/Set]] internal methods but make use of internal slots, which are accessed directly by their built-in methods, so using Get or Set handlers on them will throw errors. This is because the proxy is not the same with the target object, eg. the Map object uses [[MapData]] to store its data, so if you do proxy.get on a proxied Map, it will look for this.[[MapData]]; which is not available on the proxy, but there is a way around this limitation but to keep the article short I won’t go into details you can read about that here https://javascript.info/proxy#proxy-limitations

Closing thoughts:

In any application, which demands some extra layer of validation checks on data. Proxies can be very helpful. Using Reflect API and Proxy API very powerful.

Although accessing properties in direct way (without Proxy API) is easier to implement. Think about the applications which demands extra security like Bank applications. Go and give Proxies a shot in your applications.

Web Artisan. Human — Engineering

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store