Do you know what is Proxy in JavaScript? If not, then let's explore the curious case of Proxies in JavaScript.
I remember one of my interview few years ago. I was asked:
Given an object and how can we track when some property is accessed or updated on that object.
Example that was given by interviewer was as follows:
I was like what!!!
But any how I said to my self:
So I wrote:
Interviewer asked me about my thought process on this? After thinking and remembering a lot π€·πΌββοΈ I said, I know we have to some how intercept the read
and write
operation on the object
, but I am not sure how to do it on the fly. I know when we define properties using Object.defineProperty, we have some control on how that property will behave but the object is already defined so I am not sure. Deep inside I was still not sure π I was still thinking ππ»ββοΈ:
Interviewer dropped me a hint that if I am using Object.defineProperty
then I need to redefine the properties on the object. Then after some hit and trial I came up with this:
keys
variable.storedObject
plainObject
to storedObject
plainObject
again but this time we used Object.defineProperty
to defined it.read
property of an object and set which is called when we set a value to a property of an object.set
is called we will store that value in storedObject
and return from storedObject
when get
is called.During this time I was explaining my thought process to interviewer and I did a lot of hit and trial as it was hard for me to remember methods on Object
.
My solution has issues some issues:
enhancedObject
it will not be track
ed. By the way, interviewer asked me this
question as well π. But I could not come up with any solution back then. π
I was completely unaware that there is a better solution in ES6
. After coming home when I researched, I found out a solution which is so much easy to implement as well as to understand.
Before we jump into our solution, let's learn a bit about JavaScript Specification of Object
.
JavaScript specification describes some lower level internal methods on Object
like [[Get]]
, [[SET]]
, [[VALUE]]
, [[Writable]]
, [[Enumerable]]
and [[Configurable]]
. As per the specifications:
Each object in an
ECMAScript
engine is associated with a set ofinternal methods
that defines itsruntime behaviour
.
Point to note: It defines runtime behaviour
But we cannot directly modify the behaviour of the Object
at runtime using this so called Internal Methods
as we cannot access it directly. As per the specifications:
These internal methods are not part of the ECMAScript language. They are defined by this specification purely for expository purposes.
There are some other internal methods as well. You can checkout full list here
But in ES6
we have a way to tap into these Internal methods
at runtime.
Proxy is a middleman
. It does following:
wraps
another object.intercepts
all the operations related to Internal Methods
.wrapped object
.Proxy
is an inbuilt object that take two arguments:
Internal Methods
that we need to intercept at run time.Handler methods are often refer to as traps
because it traps or intercept the Internal method
.
Example
πΎ GOTCHA πΎ : If you do not define any handler function.
Proxy
will pass the all the operations to wrapped object as if it is not there at all.
For each of the Internal Method
there is a handler method defined on the Proxy object. Some of them are:
Internal Method | Handler Method | Triggered On |
---|---|---|
[[Get]] | get | When reading a property |
[[Set]] | set | When writing a value to a property |
[[HasProperty]] | has | When used with in operator |
[[Delete]] | deleteProperty | When deleting a property with delete operator |
[[Call]] | apply | When we do a function call |
You can refer to full list on MDN Docs and TC39 docs
There are certain condition attached to each of the handle methods. These condition must be fulfilled by the trap or handler methods. These are often referred as Invariants
. You can read more on this in note section here.
As an example for [[SET]] Operation these are the invariants as per TC39
docs:
If we set a trap for [[SET]]
operation and then we can modify the input before setting on original object name
Same as [[SET]]
trap we can set the [[GET]]
trap. Suppose when we access a property we want to print the log Getting <property_name>
. We can achieve that by using [[GET]]
trap like this:
Before I jump to Proxy
solution of the problem. There is also a sister object of Proxy
, which is known as Reflect
. As per MDN docs
Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it's not constructible.
Point to note here is
cannot
use it like new Reflect
All the methods on Reflect are static
so you can directly call them like
All the methods that you can define on the Proxy
, Reflect
has a same method
with same argument
.
Reflect can invoke the Internal Method
by using the methods defined on it.
Proxy method | Reflect call | Internal method |
---|---|---|
get(target, property, receiver) | Reflect.get(target, property, receiver) | [[Get]] |
set(target, property, value, receiver) | Reflect.set(target, property, value, receiver) | [[Set]] |
delete(target, property) | Reflect.deleteProperty(target, property) | [[Delete]] |
You can check other methods of Reflect on MDN Reflect Docs
We know there are a lot of Invariants
that we need to deal with when we trap some operation in Proxy and forward it to the original wrapped object. Remembering every rule can be hard.
We can simply use
Reflect
to forward any operation on original object and it will take care of all the Invariants
So now our [[SET]]
and [[GET]]
trap will change like this:
With Proxy
and Reflect
now we can build our solution like this:
As you can see most of latest browsers already support Proxy
except IE, Baidu and Opera. So if you do not care about these three, you can use it like a breeze.
You might be thinking, hmmmmm... this ok but what is the practical usage of this. During my research for this article I came across an example of a JavaScript framework that is utilising the powers of Proxy and that frameworkkkkkk isssss....
Vue 3
uses Proxy to be reactive and yes you got it right, Vue 3
does not support IE π. Vue 3 uses Proxy
for change detection and firing side effects.If you are not tired after reading my blog I will highly recommend you watch this free video to see full potential of Proxy
.
You can play with my solution here