
A Proxy object wraps another object and intercepts operations on it. While intercepting operations like reading, writing properties on the object, the proxy may choose to handle these operations and modify results.
Proxy
Syntax:let proxy = new Proxy(target, handler);
target
: the object that has to be proxied. handler
: the proxy configuration object, it may registertraps
. Atrap
is a handler for a particular kind of operation. By registering atrap
handler it can intercept the operation and do its own thing.
If there is atrap
for the operation onhandler
only then the operation will be trapped and handled by proxy else operation directly occurs on the object itself.
letuser={};// target object -- object to be proxiedletuserProxy=newProxy(user,{});// proxy for user, note empty handler// operations on proxyuserProxy.name='Aniket';// set operation// should be intercepted by// `set` trap on handler// no `set` trap registerd so// operations are performed on object itselfconsole.log(userProxy.name);// 'Aniket;// get opertaion// should be intercepted by `get` trap// no `get` trap registerd so opertaion// is performed on object directlyconsole.log(user.name);// 'Aniket'// Thus we can see name property// directly on target itself
For most of the operations on objects, there are “Internal Methods” in JavaScript that describes how operations work at a low level, what proxy trap does is that it can intercept these methods and do its own thing.
Below we show some of the “Internal Methods” and their corresponding proxy traps.
Internal Methods have some rules that our traps must follow, for eg:set
the trap must returntrue
if property setting was success elsefalse.[[GetPrototypeOf]]
must always return the target’s prototype when applied on proxy as well.
The problem statement
It is common practice to use
*_*
is in the beginning of the property name to denote a private property. You cannot get/set/loop this property. Write a proxy to achieve this.
letuser={name:'Aniket',_password:'Password',// private propertyisCorrectPassword(pswd){returnthis._password===pswd;// `this` here is a gotcha},};
“set” trap
We will register aset
trap on the handler to intercept write operation on the object.
Syntax:set(target, prop, value, receiver).
target
: target object. prop
: property name that is being set. value
: the value of the property to be set. receiver
: the object that is utilised as in getters.
letuserProxy=newProxy(user,{set(target,prop,value,reciver){// intercepts property writeif(prop.startsWith('_')){// check if property name start with `_`// then it is a private property so// don't allow to write or create a propertythrownewError("Access denied 💣");}else{target[prop]=val;// normally write on objectreturntrue;// must return true [[Set]] rule}}});
“get” trap
We will register aget
trap to prevent direct accessuser._password
to private property. Also, we have to ensure thatisCorrectpassword
works correctly as it does indirect accessthis._password
.
Syntax:get(target, property, receiver)
.
The arguments mean the same as above.
letuserProxy=newProxy(user,{get(target,prop,receiver){// intercept property readif(prop.startsWith('_')){// if property name starts with `_` then// we don't allow to read and raise an errorthrownewError("Access denied 💣");}else{// the property value may be a function or something elseletpropValue=target[prop];// in case it is a function// it may have `this` inside it// where `this` will ref to `userProxy`// as it will be invoked as `userProxy.isCorrectPassword(pswd)`// so `this == userProxy` but that will 🔥 our code// so we need to make sure that our function `this` ref `user`// and so we bind itreturn(typeofpropValue==="function"?propValue.bind(target):propValue);}}});
“deleteProperty” trap
We will registerdeleteProperty
so that we can't delete a private property.
Syntax:deleteProperty(target, property)
letuserProxy=newProxy(user,{deleteProperty(target,prop){// deleteProperty trap to handle property deleteif(prop.startsWith('_')){thrownewError("Access denied 💣");}else{// delete property on objectdeletetarget[prop];returntrue;// successfully deleted}}});
“ownKeys” trap
for..in, Object.keys, Object.values
and other methods utilise an “Internal Method” called[[OwnPropertyKeys]]
to get a list of keys. For eg:Object.getOwnPropertyNames()
to get a list of non-symbol keys,Object.getOwnPropertySymbols()
to get a list of symbol keys, Object.keys()
to get a list of non-symbol enumerable keys, etc.
They all call[[OwnPropertyKeys]]
but tweak it a bit to return keys according to their use case. So we will registerownKeys(target)
trap to return only public keys.
letuserProxy=newProxy(user,{ownKeys(target){// ownKeys will return a list of keys// we must get keys on target then filter// to remove all private keysreturnObject.keys(target).filter((key)=>!key.startsWith('_'));}});
Note: Our traps must follow the rules defined for “Internal Method”. The rule defined forownKeys
withObject.keys()
is that it must return non-symbol enumerable keys. Look at the example below to understand this gotcha.
letuserProxy=newProxy(user,{ownKeys(target){// this will return list of keys// and the calling method (Object.keys) tweak this list// to select and return a list of// non-symbolic and enumberable: true keys// thus for each item in list returned by ownKeys// it will only select item which is// non-symbolic and enumberable: truereturn['email','phone'];}});console.log(Object.keys(userProxy));// [] empty 😱 gotcha// solutionletuserProxy=newProxy(user,{ownKeys(target){// Object.keys will check property descriptor// for each key returned by ownKeys and see if// enumberable: truereturn['email','phone'];},getOwnPropertyDescriptor(target,prop){// checking for enumberablity of keys// is accessing its descriptor and seeing// if enumberable is true// here we are returning descriptor obj// with enumberable true in all casesreturn{enumerable:true,configurable:true,};}});```#### “has” trapThis trap work with the `in` operator that intercepts the `[[hasProperty]]` Internal Method. Let’s register a `has(target,property)` trap.```jsletrange={from:1,to:10,};// we need to check if 5 in range// 5 in range if 5 >= range.from && 5 <= range.toletrangeProxy=newProxy(range,{has(target,prop){// 5 >= 1 && 5 <= 10returnprop>=target.from&&prop<=target.to;},});console.log(5inrangeProxy);// true
“apply” trap
Until now all examples we have seen were on objects and now we will see an example offunction as target
.
Syntax:apply(target, thisArgs, args)
. thisArgs
: it is the value ofthis
args
: it is a list of arguments for function
// Let us write a function `delay`// that delay exceution of any// function `f` by `ms` milliseconds// solution 1 closure wayfunctiondelay(f,ms){returnfunction(name){// *setTimeout(()=>f.bind(this,arguments),ms);}}varhi=(name)=>{console.log('Hi!'+name);};console.log(hi.length);// 1// function.length returns number a paramshi=delay(hi,3000);// hi is now function at line *console.log(hi.length);// 0 😱// we lost orignal hi function// and function at line * has no params so 0hi('Aniket');// 'Hi! Aniket'// runs after 3s// solution 2 proxy wayfunctiondelay(f,ms){returnnewProxy(f,{apply(target,thisArgs,args){setTimeout(()=>target.bind(thisArgs,args),ms);}});}varhi=(name)=>{console.log('Hi!'+name);};console.log(hi.length);// 1hi=delay(hi,3000);console.log(hi.length);// 1 😎hi('Aniket');// 'Hi! Aniket'
The End
Now teach the Proxy you learnt here to your friend for whom you have put proxy 😂. Here is the next part of the postPart 2. Stay tuned for more content.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse