Welcome back to the blog! Today, we're diving into two powerful features introduced in ES6 (ECMAScript 2015) that unlock a new level of control and flexibility in JavaScript: Proxy
and Reflect
.
If you've ever wanted to intercept and customize fundamental operations for objects—like reading or writing properties—you're in the right place.
Before we jump into Proxy
and Reflect
, let’s briefly touch upon metaprogramming.
Metaprogramming is the practice of writing code that can inspect, modify, or control the behavior of other code.
In JavaScript, this typically means interacting with objects at a deeper level—customizing how they behave when accessed, mutated, or even defined.
Think of a Proxy
as a wrapper around an object. When you interact with the proxy, it intercepts those operations and allows you to define custom behavior for them.
A Proxy
takes two arguments:
target
object (the original object).handler
object (an object containing "trap" methods).Let’s start with a simple example.
const target = {
name: "Sahil",
age: 21,
};
const person = new Proxy(target, {
get(target, property) {
if (property in target) {
return target[property];
} else {
return `Property "${property}" does not exist on target.`;
}
},
});
console.log(person.name); // "Sahil"
console.log(person.age); // 21
console.log(person.gender); // "Property "gender" does not exist on target."
set
Let's use the set
trap to validate property values before setting them:
const target = {
name: "Sahil",
age: 21,
};
const person = new Proxy(target, {
get(target, property) {
if (property in target) {
return target[property];
} else {
return `Property "${property}" does not exist on target.`;
}
},
set(target, property, value) {
if (!(property in target)) {
console.warn(`Property "${property}" does not exist on target.`);
return false;
}
switch (property) {
case "name":
if (typeof value !== "string") {
console.error("Name must be a string.");
return false;
}
break;
case "age":
if (typeof value !== "number" || value <= 0) {
console.error("Age must be a positive number.");
return false;
}
break;
}
target[property] = value;
return true;
},
});
// Test cases
person.age = 60; // ✅ valid
person.name = "Drx"; // ✅ valid
person.age = "10"; // ❌ invalid
person.age = -1; // ❌ invalid
person.name = 12343; // ❌ invalid
With this proxy in place, invalid data won’t get silently saved. You can throw Error
insted of these console.error
Reflect
Now, when we're done validating, we usually apply the changes with:
target[property] = value;
But is this how JavaScript internally performs default behavior? Not exactly.
To apply default behavior in a more standardized and transparent way, JavaScript provides the Reflect
API.
Reflect
is a built-in object that mirrors the behavior of fundamental operations, providing default implementations for all traps available in a Proxy.
Reflect
?set
) that indicate success or failure.Let’s refactor the earlier set
trap using Reflect.set
:
const target = {
name: "Sahil",
age: 21,
};
const person = new Proxy(target, {
get(target, property) {
if(property in target){
return Reflect.get(target, property);
}
},
set(target, property, value) {
if (!(property in target)) {
console.warn(`Property "${property}" does not exist on target.`);
return false;
}
switch (property) {
case "name":
if (typeof value !== "string") {
console.error("Name must be a string.");
return false;
}
break;
case "age":
if (typeof value !== "number" || value <= 0) {
console.error("Age must be a positive number.");
return false;
}
break;
}
return Reflect.set(target, property, value);
},
});
Proxy
lets you intercept and redefine how objects behave.get
, set
, deleteProperty
, and more.Reflect
to forward behavior safely and consistently.Book a 15-minute intro call below 👇
Portfolio inspired by Lee Robinson