私有品牌检查,也称为 #foo in obj

发布时间 · 标签:ECMAScript

in 运算符可用于测试给定对象(或其原型链中的任何对象)是否具有给定属性

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

私有品牌检查功能扩展了 in 运算符,以支持 私有类字段

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

由于私有名称仅在定义它们的类内部可用,因此测试也必须在类内部进行,例如在像上面 static test 这样的方法中。

子类实例从父类接收私有字段作为自身属性

class SubA extends A {};
A.test(new SubA()); // true

但使用 Object.create 创建的对象(或通过 __proto__ 设置器或 Object.setPrototypeOf 稍后设置原型的对象)不会将私有字段作为自身属性接收。由于私有字段查找仅对自身属性有效,因此 in 运算符不会找到这些继承的字段

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

访问不存在的私有字段会抛出错误 - 与普通属性不同,访问不存在的属性会返回 undefined 但不会抛出错误。在私有品牌检查之前,开发人员被迫使用 try-catch 来实现对象没有所需私有字段情况下的回退行为

class D {
use(obj) {
try {
obj.#foo;
} catch {
// Fallback for the case obj didn't have #foo
}
}
#foo = 0;
}

现在可以使用私有品牌检查来测试私有字段是否存在

class E {
use(obj) {
if (#foo in obj) {
obj.#foo;
} else {
// Fallback for the case obj didn't have #foo
}
}
#foo = 0;
}

但要注意 - 一个私有字段的存在并不保证对象具有类中声明的所有私有字段!以下示例显示了一个半构造的对象,它只具有其类中声明的两个私有字段之一

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();

私有品牌检查支持 #