The in
operator can be used for testing whether the given object (or any object in its prototype chain) has the given property:
const o1 = {'foo': 0};
console.log('foo' in o1); // true
const o2 = {};
console.log('foo' in o2); // false
const o3 = Object.create(o1);
console.log('foo' in o3); // true
The private brand checks feature extends the in
operator to support private class fields:
class A {
static test(obj) {
console.log(#foo in obj);
}
#foo = 0;
}
A.test(new A()); // true
A.test({}); // false
class B {
#foo = 0;
}
A.test(new B()); // false; it's not the same #foo
Since private names are only available inside the class which defines them, the test must also occur inside the class, for example in a method like static test
above.
Subclass instances receive private fields from the parent class as own-properties:
class SubA extends A {};
A.test(new SubA()); // true
But objects created with with Object.create
(or that have the prototype set later via the __proto__
setter or Object.setPrototypeOf
) don't receive the private fields as own-properties. Because private field lookup only works on own-properties, the in
operator does not find these inherited fields:
const a = new A();
const o = Object.create(a);
A.test(o); // false, private field is inherited and not owned
A.test(o.__proto__); // true
const o2 = {};
Object.setPrototypeOf(o2, a);
A.test(o2); // false, private field is inherited and not owned
A.test(o2.__proto__); // true
Accessing a non-existing private field throws an error - unlike for normal properties, where accessing a non-existent property returns undefined
but doesn't throw. Before the private brand checks, the developers have been forced to use a try
-catch
for implementing fall-back behavior for cases where an object doesn't have the needed private field:
class D {
use(obj) {
try {
obj.#foo;
} catch {
// Fallback for the case obj didn't have #foo
}
}
#foo = 0;
}
Now the existence of the private field can be tested using a private brand check:
class E {
use(obj) {
if (#foo in obj) {
obj.#foo;
} else {
// Fallback for the case obj didn't have #foo
}
}
#foo = 0;
}
But beware - the existence of one private field does not guarantee that the object has all the private fields declared in a class! The following example shows a half-constructed object which has only one of the two private fields declared in its class:
let halfConstructed;
class F {
m() {
console.log(#x in this); // true
console.log(#y in this); // false
}
#x = 0;
#y = (() => {
halfConstructed = this;
throw 'error';
})();
}
try {
new F();
} catch {}
halfConstructed.m();
Private brand check support #
- Chrome: supported since version 91
- Firefox: no support
- Safari: no support
- Node.js: no support
- Babel: no support