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:
let target = {};
let proxy = new Proxy(target, {
isExtensible(trapTarget) {
return Reflect.isExtensible(trapTarget);
},
preventExtensions(trapTarget) {
return Reflect.preventExtensions(trapTarget);
}
});
console.log(Object.isExtensible(target)); // true
console.log(Object.isExtensible(proxy)); // true
Object.preventExtensions(proxy);
console.log(Object.isExtensible(target)); // false
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:
let target = {};
let proxy = new Proxy(target, {
isExtensible(trapTarget) {
return Reflect.isExtensible(trapTarget);
},
preventExtensions(trapTarget) {
return false
}
});
console.log(Object.isExtensible(target)); // true
console.log(Object.isExtensible(proxy)); // true
Object.preventExtensions(proxy);
console.log(Object.isExtensible(target)); // true
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:
let result1 = Object.isExtensible(2);
console.log(result1); // false
// throws error
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:
let result1 = Object.preventExtensions(2);
console.log(result1); // 2
let target = {};
let result2 = Reflect.preventExtensions(target);
console.log(result2); // true
// throws error
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.