Table of Contents

Note: This article does not focus on how to use Proxy. Instead, it delves into the concept of Proxy. If you are interested in learning how to use Proxy, please explore the MDN page: Proxy – MDN Web Docs
What Exactly is Proxy in Programming?
Honestly, college classes felt like a complete waste of time. There were moments when I believed that doing absolutely nothing was a better use of my time than attending classes. I wasn’t the only student with this sentiment. However, the college enforced a rule — a mandatory 75% attendance to be eligible for the final exam. I still consider it an unnecessary rule for college students. If a student can pass the exam without attending classes, they should be allowed to take the exam. After all, they are not in school anymore to debate about teaching discipline. Only a professor who has never worked in a real productive environment would argue about the importance of attending classes without answering why no one wants to attend them if they are so good.
Anyway, due to this rule, students had to devise a system. The Proxy system was a straightforward solution. You mark the attendance of your absent friend, acting as if you were your friend. You act on your friend’s behalf. This is precisely how proxies in JavaScript also work.
Suppose you want to access the property of object1
, but instead of directly modifying the property on object1
, you modify the property of object2
, which is a proxy of object1
. Let’s explore how this works and why it is useful further in this article
Magic of JavaScript Proxy
Proxies are not just a theoretical concept; if leveraged correctly, they can make our lives easier. Let’s understand it with code.
Consider the following code:
const project = {
name: 'project',
fileIds: ['file1', 'file2'],
};
const fileStorage = [
{
id: 'file1',
content: 'file 1 content',
},
{
id: 'file2',
content: 'file 2 content',
}
];
function saveFile(file) {
fileStorage.push(file);
project.fileIds.push(file.id);
}
In the above code, if we call saveFile
, you need to manually modify the project
structure as well. This is manageable for shorter code, but imagine if there are 100 of dependencies to be managed… It will be a nightmare for someone maintaining the code. But here Proxy
can rescue us from maintenance nightmare.
Here is modified code
function saveFile(file) {
fileStorage.push(file)
}
const saveHandler = {
apply(save, context, argList) {
save.apply(context, argList);
project.fileIds.push(argList[0].id);
}
}
const saveMyFile = new Proxy(saveFile, saveHandler);
and now you call saveMyFile
saveMyFile({
id: 'file 3',
content: 'this is file 3',
});
console.log(project);
// { name: 'project', fileIds: [ 'file1', 'file2', 'file 3' ] }
console.log(fileStorage);
// {
// { id: 'file1', content: 'file 1 content' },
// { id: 'file2', content: 'file 2 content' },
// { id: 'file 3', content: 'this is file 3' }
// }
This might not seem extremely useful just by looking at this code, but there are clear advantages here:
- You hijacked the call of the method, and it automatically let you control the behavior.
- Imagine if
saveFile
was a third-party utility, and you wanted to add validation on file ids and also log the call of the function. With a proxy, you could check the call of the function and do whatever you want. It is like extending the behavior of the function.
JavaScript Proxy In The Wild (In Libraries And Frameworks)
Many modern JavaScript UI libraries and frameworks leverage proxies to achieve reactivity. Svelte, Vue, React (immer) are major and most-used libraries that would not be the same without proxies.
Proxies in React using immer (Immer)
Imagine a React app with an initialState
containing a todos list. You have to be careful while mutating deep objects in React; otherwise, you may lose reactivity.
const initialState = {
label: 'This is my Todo app',
todos: [
{ id: 1, text: 'Learn Immer', completed: false },
{ id: 2, text: 'Use Immer in a project', completed: true }
],
};
const [items, setItems] = useState(initialState);
function updateTodo(newTodo, index) {
setItems({
...items,
todos: items.todos.map((todo, currentIndex) => {
if (currentIndex === index) {
return newTodo;
}
return todo;
}),
});
}
In the above example, we wrote code to avoid mutability in React. However, this is not only confusing to someone reading it but also prone to bugs.
But if we use immer, this will become really clean:
function updateTodo(newTodo, index) {
const nextState = produce(initialState, (draftState) => {
draftState.todos[index] = newTodo;
});
setItems(nextState);
}
How Leveraging Proxies Made Vue 3 Much More Reactive Than Vue 2
In Vue 2, you needed a property to exist on an object to be reactive, as it used getters and setters. If a property didn’t exist, its getters and setters would not be registered, and hence adding a property later would not be reactive.
export default {
data() {
return {
items: [
{ id: 1, name: "Item A", price: 20 },
{ id: 2, name: "Item B", price: 30, discount: 10 },
],
};
},
methods: {
addDiscount() {
console.log("xxx add discount");
// In Vue 2, dynamically adding a property will not trigger reactivity.
this.items[0].discount = 5;
},
},
};
However, this limitation has been fixed in vue 3 with the help of proxy. Following code will work without any issues.
export default {
setup() {
// Here again discount doesn't exist on the items
const items = ref([
{ id: 1, name: 'Item A', price: 20 },
{ id: 2, name: 'Item B', price: 30 },
]);
const addDiscount = () => {
// but here adding this property will make the discount reactive
items.value[0].discount = 5;
};
return { items, addDiscount };
},
};
Vue template code for both cases
<template>
<div>
<div v-for="item in items" :key="item.id">
{{ item.name }} {{ item.price }} ({{ item.discount || 0 }}% off)
</div>
<button @click="addDiscount">Add Discount</button>
</div>
</template>
Wrapping Up: Decoding the Enigma of JavaScript Proxies
As we wrap up our dive into JavaScript proxies, it’s like revealing a secret weapon in the programmer’s toolkit. From everyday problem-solving to enhancing frameworks like React and Vue, proxies prove to be versatile allies.
This isn’t just about coding jargon; it’s about giving developers the ability to shape and fine-tune their code with finesse. The real magic of JavaScript proxies shines through in practical scenarios, making React more elegant with Immer and breaking reactivity barriers in Vue 3.
In essence, JavaScript proxies are the superheroes of flexibility in the programming world. As we close this chapter, here’s to embracing their power, exploring new possibilities, and demystifying the complexities within our code.
Found any problem in this article? Email me at wtf@abhishek.wtf