The Proxy API

The ES6 Proxy API provides the Proxy constructor to create proxies. The Proxy constructor takes two arguments, which are:

  • Target: This is the object that will be wrapped by the proxy
  • Handler: This is an object that contains the traps for the target object

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";

Traps

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(target, property, receiver) method

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.

Rules

These rules shouldn't be violated while using the get trap:

  • The value returned for a property must be the same as the value of the target object property if the target object property is a non-writable, non-configurable data property.
  • The value returned for a property must be undefined if the target object property is non-configurable accessor property that has undefined as its [[Get]] attribute.

The set(target, property, value, receiver) method

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"

Rules

These rules shouldn't be violated while using the set trap:

  • If the target object property is a non-writable, non-configurable data property, then it will return as false, that is, you cannot change the property value
  • If the target object property is a non-configurable accessor property that has undefined as its [[Set]] attribute, then it will return as false, that is, you cannot change the property value

The has(target, property) method

The 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

Rules

These rules shouldn't be violated while using the has trap:

  • You cannot return false if the property exists as a non-configurable own property of the target object
  • You cannot return false if the property exists as an own property of the target object, and the target object is not extensible

The isExtensible(target) method

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"

Rules

These rules shouldn't be violated while using the isExtensible trap:

  • You cannot return false if the target is extensible. Similarly, you cannot return true if the target is non-extensible.

The getPrototypeOf(target) method

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"

Rules

These rules shouldn't be violated while using the getPrototypeOf trap:

  • It must either return an object or return null value.
  • If the target is not extensible, then this trap must return the actual prototype

The setPrototypeOf(target, prototype) method

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"

Rules

These rules shouldn't be violated while using the setPrototypeOf trap:

  • You must return false if the target is not extensible

The preventExtensions(target) method

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"

Rules

These rules shouldn't be violated while using the preventExtensions trap:

  • This trap can return true only if the target is non-extensible, or it has made the target non-extensible

The getOwnPropertyDescriptor(target, property) method

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

Rules

These rules shouldn't be violated while using the getOwnPropertyDescriptor trap:

  • This trap must either return an object or return an undefined property
  • You cannot return the undefined value if the property exists as a non-configurable own property of the target object
  • You cannot return the undefined value if the property exists as an own property of the target object, and the target object is not extensible
  • You will have to return undefined, if the property does not exist as an own property of the target object, and the target object is not extensible
  • You cannot make the configurable 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 object

The defineProperty(target, property, descriptor) method

The 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"

Rules

These rules shouldn't be violated while using the defineProperty trap:

  • It must return false if the target object is not extensible, and the property doesn't yet exist

The deleteProperty(target, property) method

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"

Rules

This rule shouldn't be violated while using the deleteProperty trap:

  • This trap must return false if the property exists as a non-configurable own property of the target object

The enumerate(target) method

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

Rules

This rule shouldn't be violated while using the enumerate trap:

  • This trap must return an object

The ownKeys(target) method

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.

Rules

These rules shouldn't be violated while using the ownKeys trap:

  • The elements of the returned array must either be a string or symbol
  • The returned array must contain the keys of all the non-configurable own properties of the target object
  • If the target object is not extensible, then the returned array must contain all the keys, of the own properties, of the target object, and no other values

The apply(target, thisValue, arguments) method

If 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

The construct(target, arguments) method

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"
The construct(target, arguments) method

The Proxy.revocable(target, handler) method

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 object
  • revoke: 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

Use Case

You can use the revocable proxies instead of the regular proxies. You can use it when you pass a proxy to a function that runs asynchronously or is parallel so that you can revoke it anytime in case you don't want the function to be able to use that proxy anymore.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.221.61