In Javascript, Proxies enables you to trap specific object operations and customize them. The Proxy acts as an intermediary between the object and the "real world". Thus, you can enhance an object basic operations to implement more complex logic or redefine fundamental operations to fit your needs.
Use cases includes:
Proxy takes two parameters:
const target = { greeting1: "Hello", greeting2: "Good morning" } const handler = { get(target, prop, receiver) { return target[prop] " friends!" } } const proxy = new Proxy(target, handler) console.log(proxy.greeting1) // Hello friends! console.log(proxy.greeting2) // Good morning friends!
In this example, we define a Proxy. The target object has two properties. We define a handler that provides an implementation of the get() handler. The get trap intercepts the access to any property on the target object, and within it, we can modify the behavior as needed.
With this setup, it means that every time we are going to access properties in the target object, the handler intercepts it, and runs the code we implemented. In our case, it just takes the property value and adds friends!.
Often times, Proxies are used with the Reflect API. Reflect provides methods with the sames names as the Proxy traps. Like its name indicates, it reflects the semantics for invoking the corresponding object internal methods.
const target = { greeting1: "Hello", greeting2: "Good morning" } const handler = { get(target, prop, receiver) { return Reflect.get(...arguments) " friends!" } } const proxy = new Proxy(target, handler) console.log(proxy.greeting1) // Hello friends! console.log(proxy.greeting2) // Good morning friends!
Reflect is not required to use Proxies but using Reflect allows us to be sure that the behavior matches the native Javascript engine operations. It also ensure compatibility with future updates, prevents unintended side effects and simplifies code. Without it, the developer would have to re-implement behavior like property access, assignment, deletion... which can be error-prone and inconsistent with Javascript's native behavior.
Let's build some examples to explore what we can do with Proxy.
In our first example, let's say we wish to log which actions are taken on a object. Whenever we get, set or delete a property, I want to be print out to the console. This could be useful for debugging purposes.
const target = { name: "Damien", age: 32, status: "WRITING" } const loggerHandler = { get(target, prop, receiver) { if (prop in target) { console.log(`[LOG] Accessing property ${prop}. Current value is ${target[prop]}`) return Reflect.get(...arguments) } else { console.error(`[LOG] Error accessing non-existent property ${prop}`) } }, set(target, key, value) { console.log(`[LOG] Setting property ${key}. New value: ${value}`) return Reflect.set(...arguments) }, deleteProperty(target, prop) { console.warn(`[LOG] Deleting property: ${prop}`) return Reflect.deleteProperty(...arguments) } } const proxy = new Proxy(target, loggerHandler) proxy.name // [LOG] Accessing property name. Current value is Damien proxy.status // [LOG] Accessing property status. Current value is WRITING proxy.name = "Bob" // [LOG] Setting property name. New value: Bob proxy.status = "NAPPING" // [LOG] Setting property status. New value: NAPPING proxy.job = "Developer" // [LOG] Setting property job. New value: Developer delete proxy.job // [LOG] Deleting property: job proxy.job // [LOG] Error accessing non-existent property job
We defined a loggerHandler that redefines 3 fundamentals operations: get, set and delete. For each action, we log something to the console describing what is happening. The beauty of the Proxy, we don't need to write the console statement everytime. We interact with our object like we always do, and the proxy takes care of the logging behavior. Pretty cool no?
In our second example, we will use a proxy to perform input validations for form data.
const validationRules = { name: value => value.length >= 3 || "Name must be at least 3 characters long", age: value => Number.isInteger(value) || "Age must be a number", email: value => value.includes('@') || "Enter a valid email" } let formData = { name: "", age: null, email: "" } const formHandler = { set(target, key, value) { if (typeof value === "string") { value = value.trim() } const validationResult = validationRules[key](value) if (validationResult !== true) { console.error(`Validation failed for property ${key}: ${validationResult}`) return false; } return Reflect.set(...arguments) } } const formProxy = new Proxy(formData, formHandler) formProxy.age = "32 years old" // Validation failed for property age: Age must be a number formProxy.name = "Da" // Validation failed for property name: Name must be at least 3 characters long formProxy.email = "damcoss mail.com" // Validation failed for property email: Enter a valid email formProxy.age = 32 // OK formProxy.name = "Damien" // OK formProxy.email = "[email protected]" // OK
We define here an object with different methods used to validate whether or not the values are valid. Then, we use the same logic. We have our target object formData that we want to proxy. In the formHandler, we redefine the set() method to apply our validation rules on the inputs values.
Proxies, combined with the Reflect API, are flexible and powerful tools to intercept and customize operations on objects. Using them, you can enhance and control behavior dynamically. By using the Reflect API, you also make sure that the behavior is consistent with the Javascript engine.
Proxies are often used in libraries and frameworks to enable advanced behabior like reactive programming, API wrappers and property observation.
Have fun ❤️
Disclaimer: All resources provided are partly from the Internet. If there is any infringement of your copyright or other rights and interests, please explain the detailed reasons and provide proof of copyright or rights and interests and then send it to the email: [email protected] We will handle it for you as soon as possible.
Copyright© 2022 湘ICP备2022001581号-3