【待做】【前端开发系列】 class 类的私有属性

o-O-oO / 2024-08-12 / 原文

https://mp.weixin.qq.com/s/f-ShUeDXUQlQIwVCrAVgSA
class 类的私有属性
前端工作室 前端精髓 2024年08月11日 10:51 北京

图片

私有属性是常规的类的公有属性(包括类字段、类方法等)的对应。私有属性通过添加 # 前缀来创建,在类的外部无法合法地引用。这些类属性的私有封装由 JavaScript 本身强制执行。

在这种语法出现之前,JavaScript 语言本身并没有原生支持私有属性。在原型继承中,可以通过使用 WeakMap 对象或者闭包的方式来模拟私有属性的行为,但就易用性而言,它们无法与 # 语法相提并论。

class ClassWithPrivate {
// 私有实例字段

privateField;

privateFieldWithInitializer = 42;

// 私有实例方法

privateMethod() {

// …

}
// 私有静态字段
static #privateStaticField;
static #privateStaticFieldWithInitializer = 42;
// 私有静态方法
static #privateStaticMethod() {
// …
}
}

还有一些额外的语法限制:

1.类中所有声明的私有标识符都必须是唯一的,并且命名空间在静态属性和实例属性之间是共享的。唯一的例外是:两个声明定义了 getter-setter 对。2.私有描述符不能是 #constructor。

大多数类属性都有其对应的私有项:

1.私有字段2.私有方法3.私有静态字段4.私有静态方法5.私有 getter6.私有 setter7.私有静态 getter8.私有静态 setter

这些特性统称为私有属性。然而,JavaScript 中构造函数不能是私有的。为了防止在类之外构造类,你必须使用私有标志。

私有属性通过“#名称”(“#”读作“hash”)来声明,它们是以 # 前缀开头的标识符。这个 # 前缀是属性名称的固有部分,你可以将其与旧的下划线前缀约定 _privateField 进行类比,但它不是普通的字符串属性,因此无法使用方括号表示法动态访问它。

在类外部引用 # 名称、引用未在类内部声明的私有属性,或尝试使用 delete 移除声明的属性都会抛出语法错误。

class ClassWithPrivateField {

privateField;

constructor() {;
delete this.#privateField; // Syntax error
this.#undeclaredField = 42; // Syntax error
}
}

const instance = new ClassWithPrivateField();
instance.#privateField; // Syntax error

JavaScript 作为动态语言,能够在编译时检查 # 标识符的语法,使其与普通属性的语法不同。

备注:Chrome 控制台中运行的代码可以访问类的私有属性。这是 JavaScript 语法限制对开发者工具的一种放宽。

如果你访问对象中不存在的私有属性,会抛出 TypeError 错误,而不是像普通属性一样返回 undefined。

class C {

x;

static getX(obj) {
return obj.#x;
}
}

console.log(C.getX(new C())); // undefined
console.log(C.getX({})); // TypeError: Cannot read private member #x from an object whose class did not declare it

这个示例也演示了你可以在静态函数中以及在外部定义的类的实例上访问私有属性。

你也可以使用 in 运算符来检查一个外部定义的对象是否拥有一个私有属性。如果对应的私有字段或私有方法存在,则返回 true,否则返回 false。

class C {

x;

constructor(x) {
this.#x = x;
}
static getX(obj) {
if (#x in obj) return obj.#x;

return "obj 必须是 C 的实例";

}
}
console.log(C.getX(new C("foo"))); // "foo"
console.log(C.getX(new C(0.196))); // 0.196
console.log(C.getX(new C(new Date()))); // 当前的日期和时间
console.log(C.getX({})); // "obj 必须是 C 的实例"

请注意,私有名称始终需要提前声明并且不可删除:如果你发现一个对象具有当前类的一个私有属性(无论是通过 try...catch 还是 in 检查),那么它一定具有其他所有的私有属性。通常情况下,一个对象具有一个类的私有属性意味着它是由该类构造的(尽管并非总是如此)。

私有属性不是原型继承模型的一部分,因为它们只能在当前类内部被访问,而且不能被子类继承。不同类的私有属性名称之间没有任何交互。它们是附加在每个实例上的外部元数据,由类本身管理。因此,Object.freeze() 和 Object.seal() 对私有属性没有影响。

微信扫一扫
关注该公众号