JavaScript ES6 - Proxy API

  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

Traps and proxy

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);
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
When accessing length < 2. violating proxy rule, hence will not update name
When validation rules defined in trap passed
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);
Passing invalid age
Passing string in age
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);
Proxy validations working — i.e. proxy running in background
Without proxy
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);
Using person1bankDetails obj — correct case
Using person1bankDetails obj — invalid a/c number
Proxies running in background
// 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
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)
Revoking after use
Without revoking — proxy has power to revoke or not based on requirements

Some Realtime examples:

a. More traps

b. Securing access with proxy

Limitations of proxies

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

--

--

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
Amir Mustafa

Amir Mustafa

JavaScript Specialist | Consultant | YouTuber 🎬. | AWS ☁️ | Docker 🐳 | Digital Nomad | Human. Connect with me on https://www.linkedin.com/in/amirmustafa1/