Metaprogramming: An Introduction to JavaScript(ES6) Proxy
The concept of Metaprogramming is not new. There are many programming languages like Lisp, Scala, Clojure, Rust, Haskell, etc already got the use of it. JavaScript is not really behind either!
Before we go any further, let us understand, What is Metaprogramming?
Metaprogramming
Metaprogramming is nothing less than a Magic! Truly, how about writing a program to Read, Modify, Analyze, and even to Generate a Program? Doesn't it sound Wizardry and Powerful? Image Courtesy: GIPHY
Wikipedia defines Metaprogramming as,
Metaprogramming is a programming technique in which computer programs have the ability to treat other programs as their data.
So basically, it is the Program that deals with the Meta Data of another program and able to do a lot of useful things.
Meet Proxy
Proxy wraps objects and intercepts their behavior through traps
Among several ways we can do Metaprogramming in JavaScript, usage of Proxy object is one of the important ones. The proxy object is an ES6 concept used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
Here are a few useful terms you need to remember and use:
- The target: an Object which the proxy virtualizes.
- The handler: a Placeholder Object which contains traps.
- The trap: the Methods that provide property access of the target object.
It is perfectly fine if you haven't got much from the description above. We will understand it very easily through code and examples.
Code Time
Here is the syntax for creating a Proxy Object:
let p = new Proxy(target, handler);
Now let us take an example of an employee
object and try to print some of the properties of it:
const employee = {
firstName: 'Tapas',
lastName: 'Adhikary'
};
console.group('employee');
console.log(employee.firstName);
console.log(employee.lastName);
console.log(employee.org);
console.log(employee.fullName);
console.groupEnd()
Well, we know the expected output would be,
employee
Tapas
Adhikary
undefined
undefined
Now let us use the Proxy
object to alter this program of employee
handling and provide some behavior to it:
- Step 1: Create a
Handler
that uses aTrap
We will be using a trap called get
which is a trap for getting a property value. Here is our Handler:
let handler = {
get: function(target, fieldName) {
if(fieldName === 'fullName' ) {
return `${target.firstName} ${target.lastName}`;
}
return fieldName in target ?
target[fieldName] :
`No such property as, '${fieldName}'!`
}
};
The above handler
helps to create the value for the fullName property. It also adds a better error message in case, we are dealing with missing property.
- Step 2: Create a
Proxy
Object
As we have the target as employee
object and the handler, we will be able to create a Proxy
object as:
let p = new Proxy(employee, handler);
- Step 3: Access the properties on the
Proxy
object
console.group('proxy');
console.log(p.firstName);
console.log(p.lastName);
console.log(p.org);
console.log(p.fullName);
console.groupEnd()
You should be seeing the output as,
proxy
Tapas
Adhikary
No such property as, 'org'!
Tapas Adhikary
Notice how we have magically changed things for the employee
object.
In the previous example, we used a trap
called get. Here is the list of available traps:
- apply
- construct
- defineProperty
- deleteProperty
- get
- getOwnPropertyDescriptor
- getPrototypeOf
- has
- isExtensible
- ownKeys
- preventExtensions
- set
- setPrototypeOf
More on these can be found here, Proxy - JavaScript | MDN
Proxy for Validation of Values
Let's create a handler(we can name it as, validator):
const validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if(!Number.isInteger(value)) {
throw new TypeError('Age is always an Integer, Please Correct it!');
}
if(value < 0) {
throw new TypeError('This is insane, a negative age?');
}
}
}
};
Again, we can create a Proxy
object as:
let p = new Proxy(employee, validator);
If you do,
p.age = 'I am testing the blunder';
The output would be a TypeError
as,
TypeError: Age is always an Integer, Please Correct it!
at Object.set (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:28:23)
at Object.<anonymous> (E:\Projects\KOSS\metaprogramming\js-mtprog\proxy\userSetProxy.js:40:7)
at Module._compile (module.js:652:30)
at Object.Module._extensions..js (module.js:663:10)
at Module.load (module.js:565:32)
at tryModuleLoad (module.js:505:12)
at Function.Module._load (module.js:497:3)
at Function.Module.runMain (module.js:693:10)
at startup (bootstrap_node.js:188:16)
at bootstrap_node.js:609:3
Similarly, try doing this!
p.age = -1;
Use-cases
Proxy Object
is a very powerful concept. There are several use-cases where this concept can be used. Here are a few:
- Protect ID field from deletion from an Object(trap: deleteProperty)
- Tracing Property Accesses(trap: get, set)
- Data Binding(trap: set)
- Revocable references
- Manipulate the
in
operator behavior
... and many many more.
Last Note
Hope you liked the concept of Proxy Object
. Try it out, it is Fun! Feel free to access the examples from My Github Repo.
'Proxy' is not the only concept for JavaScript-based Metaprogramming, learn about the Reflect APIs
from here.