Understanding what goes on behind the scenes when you operate on an object or call a method like Array.prototype.sort()
can be difficult to grasp. Using a simple Proxy can make it easier to understand. Sometimes the results will surprise you!
The Proxy
We don't need to do anything fancy, we just want to see when the object is accessed and changed, and we can do that with simple get
and set
methods:
const makeLoggingProxy = (object, title = 'Proxy') => new Proxy(object, {
get: function(target, name) {
console.log(`${title} get`, name);
return target[name];
},
set: function(target, name, value) {
console.log(`${title} set`, name, value);
return target[name] = value;
}
});
We've added an optional title
to better follow multiple Proxy objects.
Examples
You can run this code in the browser console, the Node REPL, or in my favorite tool, RunJS.
Setup
// Some color codes...mostly
const data = [
{ color: 'blue', code: '#00f'},
{ color: 'green', code: '#0f0'},
{ color: 'red', code: '#f00'},
{ color: 'amber', code: '🤔'},
{ color: 'orange', code: '#f80'},
{ color: 'magenta', code: '#f0f'},
];
const dataProxy = makeLoggingProxy(data, 'data');
Find
const result1 = dataProxy.find((entry) => entry.color === 'red');
Now, what do you think we'll see when this runs?
'data get' 'find'
'data get' 'length'
'data get' '0'
'data get' '1'
'data get' '2'
We called find
ourselves, but the find
command calls length
and iterates over the list until it finds a match, likely serving as a wrapper around the imperative for loop.
Every
const result2 = dataProxy.every((entry) => entry.code.startsWith('#'));
'data get' 'every'
'data get' 'length'
'data get' '0'
'data get' '1'
'data get' '2'
'data get' '3'
We can see that .every()
runs until it finds a falsy value, and then stops. Not every element will necessarily be checked.
for..of Loops
This is a bit of a divergence, but after seeing length
in the array methods, I wanted to see about loops.
for (let entry of dataProxy) {
// No need for any operation here
}
'data get' Symbol(Symbol.iterator)
'data get' 'length'
'data get' '0'
'data get' 'length'
'data get' '1'
'data get' 'length'
'data get' '2'
'data get' 'length'
'data get' '3'
'data get' 'length'
'data get' '4'
'data get' 'length'
'data get' '5'
'data get' 'length'
This was not what I expected at all, and gave me some new insight into the operation of the language! I suppose I could read the actual spec but sometimes trying a thing can be a good way to learn more.
Try It Yourself
If you are working with some "black box" code and are uncertain what values are being accessed or changed, a Proxy can be a useful way to see what is happening. You can also set breakpoints inside it to look at the stack trace and find places where the proxied object is used. Techniques like this can save a lot of time when troubleshooting, especially if the code is unfamiliar.
Conclusion
Small utilities like this can help us better understand the operation of our code to improve our designs and the maintainability of projects. I find this especially true in understanding array operations, which are the foundation of most data driven applications.
Acknowledgements
Header graphic was made with Microsoft Copilot.
Top comments (0)