Proxies enable us to intercept and customize operations performed on objects (such as like reading/writing properties). They are a metaprogramming feature
const target = {};
const handler = {
get(target, propKey, receiver) {
console.log(`GET ${propKey}`);
return 'Sample name';
},
has(target, propKey) {
console.log(`HAS ${propKey}`);
return true;
}
};
const proxy = new Proxy(target, handler);
console.log('proxy.name', proxy.name);
console.log('name in proxy', 'age ' in proxy);
proxy.age = 5;
console.log('target.age ', target.age);
//Outpot
//GET name
//proxy.name Sample name
//HAS age
//name in proxy true
//target.age 5
As in above code, we are trying to set age property value to 5 target.age = 5
. The proxy handler doesn’t implement the trap set (setting properties). Therefore, setting proxy.age is forwarded to target and leads to target.age being set:
If the handler does not intercept an operation, it is forwarded to target.
Objects are collections of properties but JavaScript doesn't provide any method to directly manipulate data stored in the object. For most the operations on objects, there's some internal methods specifying how it can be interacted with.
For every internal method, there’s a trap in the proxy available.
The function that define the behavior for the corresponding object internal method.
Internal Method | Trap method | Triggers |
---|---|---|
[[Get]] | get | reading a property |
[[Set]] | set | writing to a property |
[[HasProperty]] | has | in operator |
[[Delete]] | deleteProperty | delete operator |
[[Call]] | apply | function call |
[[construct]] | construct | new operator |
[[GetPrototypeOf]] | getPrototypeOf | Object.getPropertyOf |
[[SetPrototypeOf]] | setPrototypeof | Object.setPrototypeOf |
[[IsExtensible]] | isExtensible | Object.isExtensible |
[[PreventExtensions]] | preventExtensions | Object.preventExtensions |
[[DefineOwnProperty]] | defineProperty | Object.defineProperties |
[[GetOwnProperty]] getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor, for...in, Object.keys/values/entries | |
[[OwnPropertyKeys]] | ownKeys | Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in, Object.keys/values/entries |
let translate = {
"Hello": "हैलो",
"World": "वर्ल्ड"
}
const handler= {
get(target, propKey, receiver) {
if(propKey in target){
return target[propKey];
} else {
return propKey;
}
}
}
const proxy = new Proxy(translate, handler);
console.log(translate['Hello']);
console.log(translate['World']);
// output
//हैलो
//वर्ल्ड
The set trap should return true if setting is successful, and false otherwise (triggers TypeError).
let numbers = [];
numbers = new Proxy(numbers, {
set(target, prop, val) {
if (typeof val == 'number') {
target[prop] = val;
return true;
} else {
return false;
}
}
});
numbers.push(1); // added successfully
numbers.push(2); // added successfully
console.log("Length is: " + numbers.length); // 2
numbers.push("No number value"); // TypeError ('set' on proxy returned false)
console.log("error in the line above");
Reflect is a built-in global object that helps access/call object internal methods and simplifies creation of Proxy.
As we know, internal methods, such as [[Get]], [[Set]] can't be called directly.
The Reflect object makes it somewhat possible.
For every internal method, trappable by Proxy, there’s a corresponding method in Reflect, with the same name and arguments as the Proxy trap.
So we can use Reflect to forward an operation to the original object.
Operation | Reflect Call | Internal method |
---|---|---|
obj[prop] | Reflect.get(obj, prop) | [[Get]] |
obj[prop] = value | Reflect.set(obj, prop, value) | [[Set]] |
delete obj[prop] | Reflect.deleteProperty(obj, prop) | [[Delete]] |
new F(value) | Reflect.construct(F, value) | [[construct]] |
Reflect.get
is better.let student = {
_name: "Madhukar",
get name() {
return this._name;
}
}
const handler = {
get(target, prop, receiver) {
return target[prop]; // Reflect.get can be replaced here
}
}
let studentProxy = new Proxy(student, handler);
let teacher = {
__proto__: studentProxy,
_name: "Teacher"
}
console.log(teacher.name);
// Output: Madhukar
Reading teacher.name
should return "Teacher" but it returns "Madhukar".
The problem here is:
teacher.name
but teacher object doesn't have name
property.studentProxy
.proxy
prototype triggers its get
trap and returns get name
from target object.If we use Reflect
in this place, it should solve this problem.
let student = {
_name: "Madhukar",
get name() {
return this._name;
}
}
const handler = {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
}
}
let studentProxy = new Proxy(student, handler);
let teacher = {
__proto__: studentProxy,
_name: "Teacher"
}
console.log(teacher.name);
// Output: Teacher
receiver
keeps a refrence to the correct this
(teacher object), is passed to the getter of Reflect.get
.
let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('test', 1);
A notable exception: built-in Array doesn’t use internal slots. So there’s no such problem when proxying an array.
private fields are implemented using internal slots, JavaScript doesn't use [[Get]]/[[Set]] accessing them.
class Student {
name = "Madhukar";
getName() {
return this.name;
}
}
let student = new Student();
student = new Proxy(student, {});
alert(student.getName()); // Error
It can be fixed.
class Student {
name = "Madhukar";
getName() {
return this.name;
}
}
let student = new Student();
student = new Proxy(student, {
get(target, prop, receiver) {
let value = Reflect.get(...arguments);
return typeof value == 'function' ? value.bind(target) : value;
}
});
alert(student.getName()); // Guest
Proxies can’t intercept a strict equality test ===
Proxies can be revoked (disabled/switched off).
const target = {
name: 'Madhukar'
}
const handler = {};
const {proxy, revoke} = Proxy.revocable(target, handler);
console.log(proxy.name); // Madhukar
revoke();
console.log(proxy.name); // Error
After we call the function revoke for the first time, any operation we apply to proxy causes a TypeError. Subsequent calls of revoke have no further effect.