The ES6 Proxy API provides the Proxy
constructor to create proxies. The Proxy
constructor takes two arguments, which are:
A trap can be defined for every possible operation on the target object. If a trap is not defined, then the default action takes place on the target.
Here is a code example that shows how to create a proxy, and do various operations on the target object. In this example, we have not defined any traps:
var target = { age: 12 }; var handler = {}; var proxy = new Proxy(target, handler); proxy.name = "Eden"; console.log(target.name); console.log(proxy.name); console.log(target.age); console.log(proxy.age);
The output is as follows:
Eden Eden 12 12
Here, we can see that the age
property of the target
object can be accessed via the proxy
object. And when we added the name
property to the proxy
object, it was actually added to the target
object.
As there was no trap attached for the property assignment, the proxy.name
assignment resulted to the default behavior that is simply assigning the value to the property.
So, we can say a proxy
is just a wrapper for a target
object and traps can be defined to change the default behavior of operations.
Many developers don't keep a reference variable for the target object to make use of the proxy mandatory for accessing the object. Keep a reference for the handler only when you need to reuse it for multiple proxies. Here is how they rewrite the previous code:
var proxy = new Proxy({ age: 12 }, {}); proxy.name = "Eden";
There are different traps for different operations that can be performed on an object. Some of the traps need to return values. There are some rules they need to follow while returning values. The returned values are intercepted by the proxy to filter, and/or to check if the returned values obey the rules. If a trap doesn't obey rules while returning value, then the proxy throws the TypeError
exception.
The value of this
inside a trap is always a reference to the handler.
Lets take a look at the various kinds of traps.
The get
trap is executed when we retrieve a property value using the dot or bracket notation, or the Reflect.get()
method. It takes three parameters, that is, the target object, the property name, and the proxy.
It must return a value that represents the property value.
Here is a code example, which shows how to use the get
trap:
var proxy = new Proxy({ age: 12 }, { get: function(target, property, receiver){ if(property in target) { return target[property]; } else { return "Not Found"; } } } ); console.log(Reflect.get(proxy, "age")); console.log(Reflect.get(proxy, "name"));
The output is as follows:
12 Not found
Here, we can see that the get
trap looks for the property in the target
object, and if it finds it, then returns the property value. Otherwise, it returns a string indicating that it was not found.
The receiver
parameter is the reference of the object whose property we intended to access. Consider this example to better understand the value of the receiver
parameter:
var proxy = new Proxy({age: 13}, { get: function(target, property, receiver){ console.log(receiver); if(property in target) { console.log(receiver); return target[property]; } else { return "Not Found"; } } } ); var temp = proxy.name; var obj = { age: 12, __proto__: proxy } temp = obj.name;
The output is as follows:
{age: 13} {age: 12}
Here obj
inherits the proxy
object. Therefore, when the name
property was not found in the obj
object, it was looked in the proxy
object. As the proxy
object had a get
trap, it provided a value.
So, the value of the receiver
parameter when we accessed the name
property via the obj.name
expression, is obj
, and when we accessed the name
property via proxy.name
expression is proxy
.
The value of the receiver
parameter is decided in the same way for all other traps also.
These rules shouldn't be violated while using the get
trap:
undefined
if the target object property is non-configurable accessor property that has undefined
as its [[Get]]
attribute.The set
trap is invoked when we set the value of a property using the assignment operator, or the Reflect.set()
method. It takes four parameters, that is, the target object, the property name, the new property value, and the receiver.
The set
trap must return true
if the assignment was successful. Otherwise, it will return false
.
Here is a code example, which demonstrates how to use the set
trap:
var proxy = new Proxy({}, { set: function(target, property, value, receiver){ target[property] = value; return true; } }); Reflect.set(proxy, "name", "Eden"); console.log(proxy.name); //Output "Eden"
These rules shouldn't be violated while using the set
trap:
false
, that is, you cannot change the property valueundefined
as its [[Set]]
attribute, then it will return as false, that is, you cannot change the property valueThe has
trap is executed when we check if a property exists or not, using the in
operator. It takes two parameters, that is, the target object and the property name. It must return a Boolean value that indicates whether the property exists or not.
Here is a code example, which demonstrates how to use the has
trap:
var proxy = new Proxy({age: 12}, { has: function(target, property){ if(property in target) { return true; } else { return false; } } }); console.log(Reflect.has(proxy, "name")); console.log(Reflect.has(proxy, "age"));
The output is as follows:
false true
The isExtensible
trap is executed when we check if the object is extensible or not, using the Object.isExtensible()
method. It takes only one parameter, that is, the target
object. It must return a Boolean value indicating whether the object is extensible or not.
Here is a code example, which demonstrates how to use the isExtensible
trap:
var proxy = new Proxy({age: 12}, { isExtensible: function(target){ return Object.isExtensible(target); } }); console.log(Reflect.isExtensible(proxy)); //Output "true"
The getPrototypeOf
trap is executed when we retrieve the value of the internal [[prototype]]
property, using either the Object.getPrototypeOf()
method or the __proto__
property. It takes only one parameter, that is, the target
object.
It must return an object or null
value. The null
value indicates that the object doesn't inherit anything else and is the end of the inheritance chain.
Here is a code example, which demonstrates how to use the getPrototypeOf
trap:
var proxy = new Proxy({age: 12, __proto__: {name: "Eden"}}, { getPrototypeOf: function(target){ return Object.getPrototypeOf(target); } }); console.log(Reflect.getPrototypeOf(proxy).name); //Output "Eden"
The setPrototypeOf
trap is executed when we set the value of the internal [[prototype]]
property, using either the Object.setPrototypeOf()
method or the __proto__
property. It takes two parameters, that is, the target object and value of the property to be assigned.
This trap will return a Boolean, indicating whether it has successfully set the prototype or not.
Here is a code example, which demonstrates how to use the setPrototypeOf
trap:
var proxy = new Proxy({}, { setPrototypeOf: function(target, value){ Reflect.setPrototypeOf(target, value); return true; } }); Reflect.setPrototypeOf(proxy, {name: "Eden"}); console.log(Reflect.getPrototypeOf(proxy).name); //Output "Eden"
The preventExtensions
trap is executed when we prevent the addition of new properties using the Object.preventExtensions()
method. It takes only one parameter, that is, the target
object.
It must return a Boolean, indicating weather it has successfully prevented the extension of the object or not.
Here is a code example, which demonstrates how to use the preventExtensions
trap:
var proxy = new Proxy({}, { preventExtensions: function(target){ Object.preventExtensions(target); return true; } }); Reflect.preventExtensions(proxy); proxy.a = 12; console.log(proxy.a); //Output "undefined"
The getOwnPropertyDescriptor
trap is executed when we retrieve the descriptor of a property by using the Object.getOwnPropertyDescriptor()
method. It takes two parameters, that is, the target object and the name of the property.
This trap must return a descriptor object or undefined
. The undefined
value is returned if the property doesn't exist.
Here is a code example, which demonstrates how to use the getOwnPropertyDescriptor
trap:
var proxy = new Proxy({age: 12}, { getOwnPropertyDescriptor: function(target, property){ return Object.getOwnPropertyDescriptor(target, property); } }); var descriptor = Reflect.getOwnPropertyDescriptor(proxy, "age"); console.log("Enumerable: " + descriptor.enumerable); console.log("Writable: " + descriptor.writable); console.log("Configurable: " + descriptor.configurable); console.log("Value: " + descriptor.value);
The output is as follows:
Enumerable: true Writable: true Configurable: true Value: 12
These rules shouldn't be violated while using the getOwnPropertyDescriptor
trap:
undefined
propertyundefined
value if the property exists as a non-configurable own property of the target
objectundefined
value if the property exists as an own property of the target
object, and the target
object is not extensibleundefined
, if the property does not exist as an own property of the target
object, and the target
object is not extensibleconfigurable
property of the returned descriptor object false, if the property exists as an own property of the target
object, or if it exists as a configurable own property of the target
objectThe defineProperty
trap is executed when we define a property using the Object.defineProperty()
method. It takes three parameters, that is, the target
object, the property
name, and the descriptor
object.
This trap should return a Boolean indicating whether it has successfully defined the property or not.
Here is a code example, which demonstrates how to use the defineProperty
trap:
var proxy = new Proxy({}, { defineProperty: function(target, property, descriptor){ Object.defineProperty(target, property, descriptor); return true; } }); Reflect.defineProperty(proxy, "name", {value: "Eden"}); console.log(proxy.name); //Output "Eden"
The deleteProperty
trap is executed when we delete a property using either the delete
operator or the Reflect.deleteProperty()
method. It takes two parameters, that is, the target
object and the property
name.
This trap must return a Boolean, indicating whether the property was deleted successfully or not.
Here is a code example, which demonstrates how to use the deleteProperty
trap:
var proxy = new Proxy({age: 12}, { deleteProperty: function(target, property){ return delete target[property]; } }); Reflect.deleteProperty(proxy, "age"); console.log(proxy.age); //Output "undefined"
The enumerate
trap is executed when we loop over the property keys using either the for…in
loop or the Reflect.enumerate()
method. It takes one parameter, that is, the target
object.
This trap must return an iterator object, representing the enumerable keys of the object.
Here is a code example, which demonstrates how to use the enumerate
trap:
var proxy = new Proxy({age: 12, name: "Eden"}, { enumerate: function(target){ var arr = []; for(var p in target) { arr[arr.length] = p; } return arr[Symbol.iterator](); } }); var iterator = Reflect.enumerate(proxy); console.log(iterator.next().value); console.log(iterator.next().value); console.log(iterator.next().done);
Output is:
age name true
The ownKeys
trap is executed when we retrieve the own property keys using the Reflect.ownKeys()
, Object.getOwnPropertyNames()
, Object.getOwnPropertySymbols()
, and the Object.keys()
methods. It takes only one parameter, that is, the target
object.
The Reflect.ownKeys()
method is similar to the Object.getOwnPropertyNames()
method, that is, they return the enumerable and non-enumerable property keys of a object. They ignore the inherited properties. The only difference is that the Reflect.ownKeys()
method returns both, the symbol and string keys, whereas the Object.getOwnPropertyNames()
method returns only the string keys.
The Object.getOwnPropertySymbols()
method returns the enumerable and non-enumerable properties whose keys are symbols. It ignores the inherited properties.
The Object.keys()
method is similar to the Object.getOwnPropertyNames()
method, but the only difference is that the Objecy.keys()
method returns the enumerable properties only.
The ownKeys
trap must return an array, representing the own property keys.
Here is a code example, which demonstrates how to use the ownKeys
trap:
var s = Symbol(); var object = {age: 12, __proto__: {name: "Eden"}, [s]: "Symbol"}; Object.defineProperty(object, "profession", { enumerable: false, configurable: false, writable: false, value: "Developer" }) var proxy = new Proxy(object, { ownKeys: function(target){ return Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)); } }); console.log(Reflect.ownKeys(proxy)); console.log(Object.getOwnPropertyNames(proxy)); console.log(Object.keys(proxy)); console.log(Object.getOwnPropertySymbols(proxy));
The output is as follows:
["age", "profession", Symbol()] ["age", "profession"] ["age"] [Symbol()]
Here, we can see that the values of the array returned by the ownKeys
trap are filtered by the proxy, based on what the caller expected. For example, the Object.getOwnPropertySymbols()
caller expected an array of symbols. Therefore, the proxy removed the strings from the returned array.
These rules shouldn't be violated while using the ownKeys
trap:
target
objecttarget
object is not extensible, then the returned array must contain all the keys, of the own properties, of the target
object, and no other valuesIf the target is a function, then calling the proxy will execute the apply
trap. The apply
trap is also executed for function's apply()
and call()
methods, and the Reflect.apply()
method.
The apply
trap takes three parameters. The first parameter is the target
object, and the third parameter is an array, representing the arguments of the function call. The second parameter is same as the value of this
of the target function, that is, it's same as the value of this
of the target function, if the target function would have been invoked without the proxy.
Here is a code example, which demonstrates how to use the apply
trap:
var proxy = new Proxy(function(){}, { apply: function(target, thisValue, arguments){ console.log(thisValue.name); return arguments[0] + arguments[1] + arguments[2]; } }); var obj = { name: "Eden", f: proxy } var sum = obj.f(1,2,3); console.log(sum);
The output is as follows:
Eden 6
If the target is a function, then calling the target as a constructor using the new
operator or the Reflect.construct()
method will execute the construct
trap.
The construct
trap takes two parameters. The first parameter is the target
object, and the second parameter is an array, representing the arguments of the constructor call.
The construct
trap must return an object, representing the newly created instance.
Here is a code example, which demonstrates how to use the construct
trap:
var proxy = new Proxy(function(){}, { construct: function(target, arguments){ return {name: arguments[0]}; } }); var obj = new proxy("Eden"); console.log(obj.name); //Output "Eden"
A revocable proxy is a proxy that can be revoked (that is, switched off).
To create the revocable, proxies we have to use the Proxy.revocable()
method. The Proxy.revocable()
method is not a constructor. This method also takes the same arguments as the Proxy
constructor, but instead of returning a revocable proxy instance directly, it returns an object with two properties, which are the following:
proxy
: This is the revocable proxy objectrevoke
: When this function is called, it revokes the proxy
Once a revocable proxy is revoked, any attempts to use it will throw a TypeError
exception.
Here is an example to demonstrate how to create a revocable proxy and revoke it:
var revocableProxy = Proxy.revocable({ age: 12 }, { get: function(target, property, receiver){ if(property in target) { return target[property]; } else { return "Not Found"; } } } ); console.log(revocableProxy.proxy.age); revocableProxy.revoke(); console.log(revocableProxy.proxy.name);
The output is as follows:
12 TypeError: proxy is revoked
3.146.221.61