JavaScript ES6 - Proxy API
A. Basics:
- Proxy API helps in meta programming like Reflect API or Symbols.
- 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.
- 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
TRICK (what is trap):
Trap — These are set of validation/rules before accessing the object
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}// The argument of get is similar to Reflect.get()// para1 = object name// para2 = property namelet 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);// We can now use this proxy like regular objconsole.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 parameter is same as Reflect.set()’s parameter // Common rule (Validation): if val length > 2, then update a prop set: function(target, property, value) { if(value.length >= 2) { Reflect.set(target, property, value); } }}let proxy = new Proxy(person, handler);// Update a property - less length// proxy.name = 'I'; // this will not update name due to proxy ruleproxy.name = 'John Snow'; // Updates name properties// We can now use this proxy like regular objconsole.log(proxy.name); // John Snow
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// We can now use this proxy like regular objconsole.log(proxy.name);
→ 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:
Trick (When to use):
When you want to use proxy in background. You are accessing property person object name directly.
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);// This time we used person, which has proxy in backgroundconsole.log(person.name);
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'};// General Handler for objects - Check 1let handler = { get: function(target, name) { return name in target ? target[name] : 'Not existing'; }}// Handler for bank details - Check 2let 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' }}// Proxies// let proxy = new Proxy(person2bankDetails, handler);// let masterProxy = new Proxy(proxy, masterhandler);let proxy = new Proxy(person1bankDetails, handler);let masterProxy = new Proxy(proxy, masterhandler);console.log(masterProxy.account);console.log(masterProxy.bank);
TRICK (When to use):
When one proxy (general validations) requires some special validation/rules — use proxy in proxy
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);}// target = object// thisAttribute - this pointerlet 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); // no action here - as two arguments is not allowed
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);
// Revoking the function - cannot be used in future
revoke();// Suppose this code is written in future\
console.log(proxy.name)
→ 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.
Thank you for being till the end 🙌 . If you enjoyed this article, or learned something new, support me by clicking the share button below to reach more people and/or give me a follow on Twitter to see some other tips, articles, and things I learn and share there.