Let’s learn Proxy and Reflect

Nishani L. Fernando
The Startup
Published in
6 min readOct 11, 2019

Everyone is aware that the Ozone layer surrounds the earth by intercepting direct incoming energy or objects into the earth. It’s a protective layer for everything on this planet. Having that in mind let’s simply walk through what is a Proxy and what is Reflect in JavaScript.

Proxy

In general terms, Proxy is a pattern where it acts as a middle layer between two parties intercepting the communication between them. In JavaScript, Proxy is an object wrapper for another object intercepting the read, write and other object functions.

First, learn the terms that you are going to see!!

Proxy — A wrapper/layer for another object.

Target — Any object, array, function or even a proxy itself.

Trap — Methods that intercept original operations of the target object, if provided any

Handler — An object having traps or configurations. If this is empty, operations will directly happen on the target object as if there was no proxy.

A proxy accepts two parameters namely target and handler.

let proxy = new Proxy(target, handler);

If any incoming operation has a corresponding trap in the handler, then the proxy executes it. Otherwise, the original operation is executed on the target itself. Let’s look at some examples.

With empty handler — No traps

let target ={};
let handler = {}; // empty handler
let proxy = new Proxy(target, handler);proxy.myName = "John"; // adds myName to proxy, set value to "John"console.log(proxy.myName); // reads "John" from proxy
console.log(target.myName); // reads "John" from from target

In the above example, there are no traps in the handler as it is empty. Due to that, all incoming operations are passed to the target object.

We added a new property myName and set the value to “John”. It is readable from both the proxy and the target. So it’s observable that without any traps proxy is a transparent wrapper for target .

With traps in the handler

Below is a simple example of a proxy with get trap implementation.

let target = [1, 2, 3, 4]; // An array of numbers
let handler = {
get(obj, prop) {
return prop in obj? obj[prop] : 0;
}
};
let proxy = new Proxy(target, handler);console.log(proxy[0]); // reads first element from proxy -> 1
console.log(target[0]); // reads first element from proxy -> 1

Let’s get back to proxy methods. In the above examples, we set new properties, we read properties as we do with get or set methods in objects. In the lower level, there are internal methods specified for object operations. For instance, [[Get]] , reads a property and [[Set]] , writes to a property. Similarly, there are a number of internal methods listed in Proxy Specification where we can intercept with traps .

Let’s look at a set trap implementation.

Note that theset trap should return true on a successful set execution, otherwise false . If not returned either it will throw a TypeError . The same conditions apply on [[Delete]] as well. Likewise, the aforementioned specification imposes some conditions on several internal methods while writing traps on them.

let target = []; // An array of numbers
let handler = {
set(obj, prop, val) {
if(typeof val === 'number') {
obj[prop] = val;
return true;
} else {
return false;
}
}
};
let proxy = new Proxy(target, handler);
proxy.push(1);
proxy.push(2);
console.log(target.length); // returns 2
proxy.push("hello"); // TypeError : 'set' on proxy: trap returned false

Reflect

Reflect makes our lives easier by simplifying the creation of Proxy. It’s like syntactic sugar for Proxy object creation and use. With Reflect we can simply pass on the parameters along with the corresponding method invocation.

In Proxy, handler methods are triggered on certain events that cause it to happen. For instance, construct handler is triggered with new operator, get is triggered while reading a property and set is triggered while writing to a property. Likewise, Reflect facilitates us to call such operators as functions. Below are a few examples of such function definitions.

Reflect.get(obj, prop) // reading

Reflect.set(obj, prop, value) // writing

Reflect.deleteProperty(obj, prop) // deleting a property

Reflect.construct(F, value) // invoke new to create an object

See the following example of set in use.

let student = {};Reflect.set(student, 'name', 'John');console.log(student.name); // John

Simply for every internal method, which can be trapped with Proxy has a corresponding function in Reflect having the same name and arguments defined in Proxy trap.

Limitations

Given that proxies can provide us a way to tweak the behavior of existing objects, there are downsides or limitations over some of the internal behaviors of built-in objects/elements.

Built-in objects using Internal slots

Many built-in objects like Map, Set, Promise or Date utilize hidden/reserved properties called “internal slots” which cannot be intercepted using Proxy . For instance, Map stores items in [[MapData]] internal slot which are directly accessible via built-in methods, but not via [[Get]]. If we try to intercept that kind of object, it will fail since Proxy doesn’t have these internal slots or its behaviors.

Look at the following example.

let m = new Map();m.set('hello', 1); // {"hello" => 1}let proxy = new Proxy(m, {}); // empty handlerproxy.set('test', 2); // will give an error

We created a Map and added a new key-value pair. Then we tried the same on the proxied map and that gave us an error. Why? Because Map stores its data in [[MapData]] an internal slot. Our proxy doesn’t have such a slot. When the built-in Map.prototype.set tried to access this.[[MapData] internal property where this = proxy and the proxy which doesn’t have one as such it returned an error.

We can overcome this situation by adding get trap which binds the function properties to the target object Map (m) itself. Observe the following example.

let m = new Map();let proxy = new Proxy(map, {
get(target, prop, receiver) {
let value = Reflect.get(...arguments);
return typeof value == 'function' ? value.bind(target) : value;
}
});
proxy.set('hello', 1); // Works fine
console.log(proxy.get('hello')); // Returns -> 1

Now you might have a doubt on “Okay, how this works for Array?”

The answer is simple because the built-in Array doesn’t use internal slots.

Private fields will be new in JavaScript Classes which are also implemented using internal slots. As a matter of fact, a proxied object instance will throw an error if we try to access a private property via a get method.

class Student {
#nickName = "Nicky"; // Already set for the getter demonstration

getNickName() {
return this.#nickName;
}
}
let student = new Student();student = new Proxy(student, {});console.log(student.getNickName()); // Error

Note that [[Get]] or [[Set]] are not used while accessing private fields.

This situation can also be overcome by the previous strategy itself.

class Student {
#nickName = "Nicky"; // Already set for the getter demonstration

getNickName() {
return this.#nickName;
}
}
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;
}
});
console.log(student.getNickName()); // Error

Important!!!

Proxy != target

Although we intercept the target using a Proxy, those objects are not the same. As explained earlier, it’s just a gateway to our target object.

Example:

let students = new Set();class Student {
constructor(name) {
this.name = name;
students.add(this);
}
}
let student = new Student("Bob");
console.log(students.has(student)); // returns true
student = new Proxy(student, {}); // re-assign with Proxy
console.log(students.has(student)); // returns false

Use Cases

Following is a use case where Proxy or Reflect can be useful. You can look for more use cases here.

Validation

You can add custom validation checks for any object with a Proxy by trapping Set handler.

let validator = {
set(obj, prop, value) {
if (prop === 'mark' && !Number.isInteger(value)) {
throw new TypeError('The mark is not an integer');
}
// store the value
obj[prop] = value;
// trap should return true on success
return true;
}
};
let subject = new Proxy({}, validator);subject.mark = 100;
console.log(subject.mark); // 100
subject.mark = '100'; // Throws an exception

You can seek other trappable methods in addition to the methods mentioned in this article. Hope you learned something useful.

--

--

Nishani L. Fernando
The Startup

Full Stack Software Engineer and passionate about Nature, Science & Technology