DEV Community

Cover image for Detect object changes with JavaScript Proxy
Chris Bongers
Chris Bongers

Posted on • Originally published at daily-dev-tips.com

Detect object changes with JavaScript Proxy

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,
};
Enter fullscreen mode Exit fullscreen mode

Now the user changes his age by using the following code.

user.age = 33;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
  },
};
Enter fullscreen mode Exit fullscreen mode

Let's try it out by modifying the age again.

proxyUser.age = 33;
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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;
  },
};
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
yuridevat profile image
Julia πŸ‘©πŸ»β€πŸ’» GDE

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. πŸ˜…

Collapse
 
dailydevtips1 profile image
Chris Bongers

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.

Collapse
 
yuridevat profile image
Julia πŸ‘©πŸ»β€πŸ’» GDE • Edited

Thank you so much for your answer, Chris. I really appreciate it.

Collapse
 
lexlohr profile image
Alex Lohr

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.

Collapse
 
peerreynders profile image
peerreynders • Edited

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:

  • Content is the foundation
  • Markup is an enhancement (HTML)
  • Visual Design is an enhancement (CSS)
  • Interaction is an enhancement (JS)

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.

Collapse
 
arthurdenner profile image
Arthur Denner

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.

Collapse
 
lexlohr profile image
Alex Lohr

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:

const _user = {
  firstName: 'Chris',
  lastName: 'Bongers',
  age: 10,
};
const user = {};
Object.defineProperties(
  user,
  Object.getOwnPropertyNames(_user)
    .reduce((properties, prop) => {
      properties[prop] = {
        get: () => _user[prop],
        set: (value) => {
          console.log(`changed ${prop} from ${_user[prop]} to ${value}`;
          _user[prop] = value;
          return value;
        }
      };
      return properties;
    }, {})
);
Enter fullscreen mode Exit fullscreen mode

However, that only intercepts uses of existing properties.

Collapse
 
dailydevtips1 profile image
Chris Bongers

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?

Collapse
 
lexlohr profile image
Alex Lohr

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.

Thread Thread
 
dailydevtips1 profile image
Chris Bongers

Makes sense, thanks for that info 🀘

Thread Thread
 
jsagon profile image
Jhonatan S. GonΓ§alves • Edited

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.

Image description

Thread Thread
 
lexlohr profile image
Alex Lohr

The setup performance in this case is obviously better for proxy, since it's native. I was talking about access.

 
peerreynders profile image
peerreynders • Edited

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.

favicon indepth.dev

GitHub logo WebReflection / proxy-pants

Secured and reliable Proxy based utilities for more or less common tasks.

proxy-pants

build status Coverage Status CSP strict

Social Media Photo by lan deng on Unsplash

Secured and reliable Proxy based utilities for more or less common tasks:

  • accessor to trap one or more accessors for any object
  • applier & caller to trap any borrowed callback/utility without needing to use .call or .apply to pass the context
  • bound to bind one or more methods all at once
  • bread & crumbs to track operations through paths (i.e. a.b.c.d) and namespaces
  • cache to compute once any accessed property through a proxied, and secured, map
  • chain to trap once all inherited descriptors down the prototypal chain and automatically ensure the right accessor or method
  • dsm to virtually trap dataset / *set accessors as DOMStringMap like references per each element. Please note this utility is not secured
  • extender to extend any object through weakly referenced behaviors, providing a new way to deal with state machines too, through the following features
    • …
Collapse
 
omarbenmegdoul profile image
Omar Benmegdoul

Why?

Collapse
 
dailydevtips1 profile image
Chris Bongers

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.

 
omarbenmegdoul profile image
Omar Benmegdoul

Speaking of which, is this how react state is implemented?