Object Extensibility Traps

ECMAScript 5 added object extensibility modification through the Object.preventExtensions() and Object.isExtensible() methods, and ECMAScript 6 allows proxies to intercept those method calls to the underlying objects through the preventExtensions and isExtensible traps. Both traps receive a single argument called trapTarget that is the object on which the method was called. The isExtensible trap must return a boolean value indicating whether the object is extensible while the preventExtensions trap must return a boolean value indicating if the operation succeeded.

There are also Reflect.preventExtensions() and Reflect.isExtensible() methods to implement the default behavior. Both return boolean values, so they can be used directly in their corresponding traps.

Two Basic Examples

To see object extensibility traps in action, consider the following code, which implements the default behavior for the isExtensible and preventExtensions traps:

  1. let target = {};
  2. let proxy = new Proxy(target, {
  3. isExtensible(trapTarget) {
  4. return Reflect.isExtensible(trapTarget);
  5. },
  6. preventExtensions(trapTarget) {
  7. return Reflect.preventExtensions(trapTarget);
  8. }
  9. });
  10. console.log(Object.isExtensible(target)); // true
  11. console.log(Object.isExtensible(proxy)); // true
  12. Object.preventExtensions(proxy);
  13. console.log(Object.isExtensible(target)); // false
  14. console.log(Object.isExtensible(proxy)); // false

This example shows that both Object.preventExtensions() and Object.isExtensible() correctly pass through from proxy to target. You can, of course, also change the behavior. For example, if you don’t want to allow Object.preventExtensions() to succeed on your proxy, you could return false from the preventExtensions trap:

  1. let target = {};
  2. let proxy = new Proxy(target, {
  3. isExtensible(trapTarget) {
  4. return Reflect.isExtensible(trapTarget);
  5. },
  6. preventExtensions(trapTarget) {
  7. return false
  8. }
  9. });
  10. console.log(Object.isExtensible(target)); // true
  11. console.log(Object.isExtensible(proxy)); // true
  12. Object.preventExtensions(proxy);
  13. console.log(Object.isExtensible(target)); // true
  14. console.log(Object.isExtensible(proxy)); // true

Here, the call to Object.preventExtensions(proxy) is effectively ignored because the preventExtensions trap returns false. The operation isn’t forwarded to the underlying target, so Object.isExtensible() returns true.

Duplicate Extensibility Methods

You may have noticed that, once again, there are seemingly duplicate methods on Object and Reflect. In this case, they’re more similar than not. The methods Object.isExtensible() and Reflect.isExtensible() are similar except when passed a non-object value. In that case, Object.isExtensible() always returns false while Reflect.isExtensible() throws an error. Here’s an example of that behavior:

  1. let result1 = Object.isExtensible(2);
  2. console.log(result1); // false
  3. // throws error
  4. let result2 = Reflect.isExtensible(2);

This restriction is similar to the difference between the Object.getPrototypeOf() and Reflect.getPrototypeOf() methods, as the method with lower-level functionality has stricter error checks than its higher-level counterpart.

The Object.preventExtensions() and Reflect.preventExtensions() methods are also very similar. The Object.preventExtensions() method always returns the value that was passed to it as an argument even if the value isn’t an object. The Reflect.preventExtensions() method, on the other hand, throws an error if the argument isn’t an object; if the argument is an object, then Reflect.preventExtensions() returns true when the operation succeeds or false if not. For example:

  1. let result1 = Object.preventExtensions(2);
  2. console.log(result1); // 2
  3. let target = {};
  4. let result2 = Reflect.preventExtensions(target);
  5. console.log(result2); // true
  6. // throws error
  7. let result3 = Reflect.preventExtensions(2);

Here, Object.preventExtensions() passes through the value 2 as its return value even though 2 isn’t an object. The Reflect.preventExtensions() method returns true when an object is passed to it and throws an error when 2 is passed to it.