DEV Community

Cover image for Learn by Proxy: JavaScript Quick Bits
Samuel Rouse
Samuel Rouse

Posted on • Edited on

Learn by Proxy: JavaScript Quick Bits

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

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

Find

const result1 = dataProxy.find((entry) => entry.color === 'red');
Enter fullscreen mode Exit fullscreen mode

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

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('#'));
Enter fullscreen mode Exit fullscreen mode
'data get' 'every'
'data get' 'length'
'data get' '0'
'data get' '1'
'data get' '2'
'data get' '3'
Enter fullscreen mode Exit fullscreen mode

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

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)