Fun with JavaSript: Proxy Object
Use a Proxy Object to create a simple Lodash Get/Set clone
JavaScript is wild and ever-changing. Things we only dreamed about doing last year are a reality today. It’s super exciting!
Today I want to talk to you all about JavaScript Proxy Objects. I’ll go into a little bit of the definition of a Proxy Object, some simple use cases, and finally an example of cloning functionality from the awesome Lodash library using JavaScript Proxy Objects.
Some Helpful Links:
What is a Proxy Object?
A Proxy Object is a device that enables you to create a proxy from another object. You can then use this Proxy to redefine operations on the object such as get, set, and delete. When you create a Proxy you can pass two arguments — the target
which is just a plain ole JavaScript object, and a handler which is used to define the methods you want to intercept on the object.
Check out this very simple implementation of a JavaScript Proxy:
Adding Custom Handlers
The main benefit of using a Proxy Object is the ability to redefine
object operations using custom handler
functions. The example below shows a custom get
handler function that captures prop2
on the object and returns a different value.
Abstracting Handlers to their own Functions
Abstraction is key to a clean code base (IMO and if done right). With , we can abstract Proxy Object Handlers to their own functions, but be warned that arrow functions () => {}
In the above example we have moved our proxy get
and set
handlers into proxyGetFn
and proxySetFn
respectively. Then we simply pass them to our handler and create a new Proxy
to see the magic happen.
Cloning Lodash Get & Set
For the purpose of this article we will be mimicking the Get
and Set
functionality from Lodash by modifying the get
and set
handlers on the Proxy Object. Specifically, we want the ability to pass property paths like 'parent.child.grandchild'
, ['parent', 'child', 'grandchild']
and 'parent[child].grandchild'
to our object and get (or set) the right value.
What is Lodash?
Lodash is A modern JavaScript utility library delivering modularity, performance & extras.
Lodash is a JavaScript library that makes working with object (numbers, strings, functions, arrays, etc…) extremely easy. The library is comprised of a number of tools that help developers manipulate objects in ways that are both performant and intuitive.
If you want a tried and tested library that can deliver some great functionality for working with JavaScript objects, then please use Lodash (or similar libraries).
First, Some Helper Functions
Before we start updating our proxy handlers, let’s create a few helper functions.
The first is replaceBrackets
which helps us use property paths like parent[child].grandchild'
by finding anything in []
(inclusive) and replacing it with the dot notation form.
It uses the bracketCaptureRegex
to …well… capture the brackets.
Next we have makeSegments
which converts our property path into an Array of string or symbol
to use in our handlers. This function uses the pathMatchRegex
to test for periods and commas* in strings and the pathSplitRegex
to split the matching strings into an Array.
* We are checking for commas because passing an array as the key in bracket notation forces the array into a comma delimited
Using Reflect and Recursion to Enhance Custom Handlers
The first step to creating our Lodash clone is to update our Proxy functions to use our makeSegments
helper function and recursively iterate through the segments until we get/set the value.
In the proxyGetFn
function we start off by testing whether or not the target exists. If it doesn’t then we return undefined
. We move on to creating our segments and using the first item of the segments array as our key
. and split rest
into another array.
If rest
has a length greater than 0, we recursively call proxyGetFn
with a new target (using Reflect.get
), and the rest
of our segment.
Finally, we check if the target
has the key
using Reflect.has
and return the value using Reflect.get
In the proxySetFn
function we skip the check for the target and instead define our key
and rest
using the makeSegments
helper function.
Next we check if rest
has a length greater than 0. If it does, we then proceed to check if the target
contains the key
using Reflect.get
. If that is false, we return undefined (similar to the target check in proxyGetFn
). If the target
does have the key
, we recursively call proxySetFn
(also similar to proxyGetFn
)
Finally, if all is well, we can set our value!
Converting your Objects to Proxies
Our next step is to make it a easier to convert our objects to proxies. Sure you could set up a new Proxy()
every time, but that would just bloat your code. Instead we can define a createProxy
function that takes in an object and spits out a proxy with our custom handlers applied.
Testing Your New Proxy Object
If you’re a bit suspicious that this createProxy
abstraction would just work (which you should be… don’t trust me… 👀 ) you can opt to write some tests. For your sake I’ve printed out a few to ease your mind.
We create a proxy
object using createProxy
and test that it returns some values. We can even add some complex property paths like complexArr.1.value
and get back the right value.
Something really neat is that you can also set a value for an index that doesn’t yet exist in an array. Not that you ever would… but think about all those times where you just wanted to easily set a value at the nth index in an array dynamically and couldn’t!
Well now I… I mean you can.
Making our Lodash Clone
We’re near the end! All that’s left is to create our Lodash clone.
To do this we create a variable _ and set it to an object with get
and set properties.
_.get takes in an object, a path, and a defaultValue. It then uses createProxy to create a new Proxy Object with our custom handlers. We then pass the path into the proxyObj
and either return the value it finds, or the defaultValue if nothing is found.
_.set takes in an object, a path, and a value. It also creates a new Proxy object and tries to set the value to the path. If the new value is found it then mutates (really updates) the original object using structureClone**. If the new value is not found, it returns undefined.
** We use structuredClone to to update the object with the new values of the Proxy Object because proxy ≠ object.
Using our Lodash Clone Methods
If you’re a bit suspicious that this Lodash clone would just work (which you should be… don’t trust me… 👀 )(wait…) then you should definitely test it out.
Just to make things a bit easier on you I’ve logged some results below.
As you can see, we can get values with property matchers like values.0
, [‘values’, 1, ‘value’]
and even ‘values[1]’
which makes our _.get and _.set pretty dang close to the ones in Lodash.
I’d call this a success!
A thought exercise
Given that we’ve created get
and set
methods in our Lodash clone… how would you go about creating a similar omit
or pick
method using what you learned today?
Finally we’re Done! If you enjoyed this article or learned something new, please leave a clap or two. 👏🏾
Thanks for taking the time to read my article. Keep on the lookout for my next one:
Using the JavaScript Proxy Object as a React State Manager
Until then, I wish you all the success in the world on your next project!