March 31, 2025
"With great power comes the ability to absolutely wreck your own code."
JavaScript is like a Multi-Tool. It has a specific tool for everything, but mostly we ignore the weird looking ones and just use the pliers and sometimes the screwdriver. I mean, it does solve most of our problems, right?
For example, whenever a bug comes along, the goto is console.log
. If that doesn't work, use more console.log
statements. When that doesn't work we either cry or use the debugger
.
But what if I were to tell you that there is a secret third option? A middle-ground between console.log
and debugger
that can help you debug your code in a more elegant way. Enter Proxy.
"The
Proxy
object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object." - MDN
But what does that mean? JavaScript’s Proxy
object is like middleware for your Objects
. It lets you dig into various operations like property access, assignment, enumeration, function calls. You can then intercept them, modify them, or just watch them happen.
This is what it looks like:
// The object I wanna spy on
const target = { message: 'Hello World' }
// The watcher that'll do the spying
const handler = {
get(target, prop) {
console.log(`Accessed: ${prop}`)
return target[prop]
},
}
// Proxy connects the two
const proxy = new Proxy(target, handler)
console.log(proxy.message) // Logs "Accessed: message" then "Hello"
The Proxy
takes two things:
Every time something touches the proxy
, your handler can intercept it. This is known as a trap
, which I love. But remember, with great power... comes an even greater ability to make more bugs. So let's see how we can use this power for good.
Create a generic handler object that just logs events. Having difficulty figuring out an objects values and want to spy on it? Just import the utility and wrap it in a Proxy to log every read/write. Now you don't have to worry about adding/removing log statements throughout your code.
const debugProxy = (obj) =>
new Proxy(obj, {
get(target, prop) {
console.log(`[GET] ${String(prop)}`)
return Reflect.get(...arguments)
},
set(target, prop, value) {
console.log(`[SET] ${String(prop)} = ${value}`)
return Reflect.set(...arguments)
},
})
const user = debugProxy({ name: 'Kaemon', level: 99 })
user.name // [GET] name
user.level = 100 // [SET] level = 100
Perfect for hunting down rogue state mutations or just spying on your app like a digital goblin. Speaking of mutations, you may have noticed that I've used Reflect
in the handler. This post won't go into detail on Reflect, all you need to know is that it provides default behavior for the traps. You can use it to call the default behavior of the operation you're intercepting. This prevents mutation and allows safe spying without creating even more bugs.
Wanna take Proxy
even further and use it as part of your everyday code? You can give your objects default responses! No more undefined
errors when you forget to set a value.
const config = new Proxy(
{},
{
get: (_, prop) => `Missing config: ${String(prop)}`,
},
)
console.log(config.apiKey) // "Missing config: apiKey"
Now you can spot missing values at a glance, without your app crashing halfway through rendering. Better yet, you can use it to set default values for your objects.
Want it to fall back to specific defaults instead of generic warnings? You can combine Proxy with a defaults object like this:
const defaults = { name: 'Anonymous', age: 0 }
const userData = new Proxy(
{},
{
get: (target, prop) => (prop in target ? target[prop] : defaults[prop]),
},
)
Now you can access userData
without worrying about missing properties. It'll just fall back to the defaults you set. No more undefined
errors, no more tears.
Why wait for bugs when you can yell at yourself in real time? Use Proxy
to validate your object properties as they're set.
const userData = new Proxy(
{},
{
set(target, prop, value) {
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('Age must be a number')
}
return Reflect.set(...arguments)
},
},
)
userData.age = 30 // 👍
userData.age = 'thirty' // 💥 TypeError
You can even combine this with forms to validate user input without a mountain of if-checks.
There are endless possibilities with Proxy
, you can even use it to build your own API clients, or a reactive state management system. As you can see, Proxy
is powerful. But like most powerful tools, it’s easy to abuse.
You can easily make your life even harder by overusing Proxy
. Here are some things to consider before you go all-in:
You should use it when you want to:
console.log
statements.undefined
errors with default values.Proxy
to log and spy on objects.undefined
.Proxy
is a powerful tool that can help you debug your code in a more elegant way. But like any tool, it has its place. This shouldn't be the new console.log
, but it can be a powerful ally in your debugging arsenal.
Go forth and intercept, my chaotic coder. Just remember to use your powers for good, not evil! Got a cursed or clever Proxy trick? If you're interested in reading more of my thoughts, check out my blog at The Glitched Goblet.
<!-- psst... try console.log(window.glitch) on my blog... -->