Let's say we have an object for our users. How can we detect when a property changes?
const user = {
firstName: 'Chris',
lastName: 'Bongers',
age: 10,
};
Now the user changes his age by using the following code.
user.age = 33;
However, we want to log this change to keep track of specific changes.
Using JavaScript Proxy to detect object changes
That's again where the Proxy object comes in handy.
As we learned in the previous article, the Proxy object comes with a set
handler (trap).
The set
handler can pass us the following three parameters.
- object: The whole object we are trying to modify
- property: The property we are trying to modify
- value: The new value we are trying to set
Let's create a proxy to our user object so we can attach a handler.
const handler = {
set(target, prop, value) {
// Our code
},
};
const proxyUser = new Proxy(user, handler);
As for the code, we want to log which property is being changed, what the previous value was, and what the new value will be.
Then we need to ensure that we set the new value.
The result is the following function.
const handler = {
set(target, prop, value) {
console.log(`changed ${prop} from ${target[prop]} to ${value}`);
target[prop] = value;
},
};
Let's try it out by modifying the age again.
proxyUser.age = 33;
Now the console will show this change and log changed age from 10 to 33
.
Detecting additional Object properties
Sometimes we might push new properties to the object. Let's see how we can capture that.
proxyUser.email = 'info@daily-dev-tips.com';
And again, this will neatly log the change: changed email from undefined to info@daily-dev-tips.com
.
However, there is one small exception.
If we have a sub-object or array in our main object, it won't work out of the box.
const user = {
firstName: 'Chris',
lastName: 'Bongers',
age: 10,
address: {
postalCode: '1234AB',
},
};
proxyUser.address.postalCode = '5678CD';
This now logs nothing new, but the property is changed!
And that's because there is now a deep proxy set.
To log on that level, we can again leverage the get
handler and proxy each property to be a proxy itself π€―.
const handler = {
get(target, key) {
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler);
}
return target[key];
},
set(target, prop, value) {
console.log(`changed ${prop} from ${target[prop]} to ${value}`);
target[prop] = value;
},
};
And now, when we rerun our code, we see the log appear as changed postalCode from 1234AB to 5678CD
.
I added these examples to CodePen so you can try them out yourself.
Thank you for reading, and let's connect!
Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter
Top comments (16)
Thank you for the great Proxy Series coming out now.
What I am always struggling with is (with JavaScript), that I am not able to get when I would use it. I would be happy if there would be a styled frontend to it, which would finally help me getting a deeper understanding of JavaScript.
Like, do you use it when I have an account on a webapp where I had to fill out my personal data, saved it, and realized that I got something wrong? Like in a CRUD App?
This should not be a criticism to your article because in fact it is really good explained but more of a "sigh" again to myself that I am not able to get a deeper understanding of JavaScript because without the frontend I cannot quite understand. π
Hi Julia,
This is indeed always a tricky question and like @lexlohr mentions: it depends.
When it comes to things like
Proxy
trust me, you are very unlikely to need them.And when you do, you'll know it.
I think often there are so many ways to do the same things in JavaScript that there is no real right or wrong.
However there are always optimisations to achieve.
Something of which Alex again is super good at.
I really wish there was some kind of visual representation on when to use what, but at least to me it's not as simple to explain it in such way.
I'll provide some more details on when I used this Proxy for the first time in tomorrows article, hope that helps a bit.
Thank you so much for your answer, Chris. I really appreciate it.
If I understand you correctly, you are asking when to use which solution. Unfortunately, as so often in development, the answer is: it depends.
A simple form to get personal data would better solved as a static page with a server backend, since for security reasons, you need to validate the data on the server in any case. If you got that, you can progressively enhance with JS. However, if that form is part of an app that requires JS anyway, you can write it as part of that app (just don't forget the server validation).
That being said, what you miss is not understanding of JS, but understanding of development patterns.
After doing some digging I came across Manuel MatuzoviΔ's article How To Build A Progressively Enhanced, Accessible, Filterable And Paginated List.
Unfortunately it uses Alpine.js rather than vanilla JS which would have been more instructive but given that it also provides a github repo and codepen it might be a good candidate for "poking and prodding" to figure out how things work (i.e. don't read too much into the particular technologies that go into this example).
Summarizing progressive enhancement:
This is part of the web's approach to resilience; fundamentally JavaScript is the least important layer of the web.
This realization came after it became clear that "graceful degradation" does not work.
However about the same time (~2010) another architectural style emerged: the Single Page Application (SPA).
SPAs turned the web on its head; JavaScript was no longer the least important aspect but JavaScript became the most important aspect because JavaScript became responsible for everything (completely sidelining the browser's capabilities to parse HTML and process CSS even before any JavaScript started executing).
That situation has contributed to a lot of the confusion around the role of JavaScript on the web.
It's only recently that the industry has started paying attention to progressive enhancement again, due to the renewed interest in server-side rendering.
From a recent project: we had an object passed to N "loader functions" depending on the URL requested. We changed the signature of these loader functions but we couldn't remove the object while some of these loader functions used it.
I used a Proxy to detect and report access and calls to properties/methods on the deprecated object. This way we could track who, where and what was being accessed/called and act accordingly.
Like others said, it's good to understand Proxies, they are very powerful. It was the first time I used them on a production project but as soon as I got the requirement, I knew a Proxy would be a good fit.
Nice write-up, especially the part about deep proxies. However, it would have been nice to show how proxies merely wrap objects in setters/getters, which you can do manually in order to increase performance:
However, that only intercepts uses of existing properties.
Hey Alex,
I'm aware of the underlying wrapping, but yet thought this would even confuse people more.
The one thing i'm not really aware of, how much performance loss are we talking about when comparing proxy to its native alter ego?
It won't matter below a few thousands of accesses per second, so it will probably be more relevant to benchmarking than to actual applications. Still, I have encountered some real-life applications that would have done better in terms of performance without extensive proxying.
Makes sense, thanks for that info π€
Hi, Chris and Alex
I was curious about the performance difference, and I tried the jsbench to see the result. In every test the Proxy is the fastest one.
The setup performance in this case is obviously better for proxy, since it's native. I was talking about access.
MobX, SolidJS Stores (not to be confused with signals) and Vue use Proxies.
Exploring the state of reactivity patterns inΒ 2020 - JavaScript inDepth
Exploring the trend that has already changed the shape of front end UI development regardless of framework.
WebReflection / proxy-pants
Secured and reliable Proxy based utilities for more or less common tasks.
proxy-pants
Social Media Photo by lan deng on Unsplash
Secured and reliable Proxy based utilities for more or less common tasks:
.call
or.apply
to pass the contexta.b.c.d
) and namespacesdataset
/*set
accessors as DOMStringMap like references per each element. Please note this utility is not securedWhy?
Missing a bit of context here.
I use it outside a library or framework.
It's a valid option when you need it.
Basically mocking existing methods, for very specific use-cases.
However. agree only use this when you have no other alternative.
Speaking of which, is this how react state is implemented?