7

Private brand checks a.k.a. #foo in obj

 3 years ago
source link: https://v8.dev/features/private-brand-checks
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Private brand checks a.k.a. #foo in obj

Published 14 April 2021 · Tagged with ECMAScript

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.setProrotypeOf(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 #


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK