作为ES2015
的一部分,它支持公共字段(没有字段语法)、公共 getters
/ setters
和公共方法,以及公共静态 getters
/ setters
和公共静态方法。
下面是一个包含上面体到的所有方法的例子:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName; // 公共字段
this.lastName = lastName; // 公共字段
}
// 公共 getter
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
// 公共 setter
set fullName(value) {
const parts = value.split(" ");
this.firstName = parts[0];
this.lastName = parts[1];
}
// 公共方法
introduceYourselfTo(other) {
const name = other.firstName ?? other;
console.log(`Hello ${name}! My name is ${this.fullName}.`);
}
// 公共静态 getter
static get typeName() {
return "Person";
}
// 公共静态方法
static fromJSON(json) {
return new Person(json.firstName, json.lastName);
}
}
const john = new Person("leo", "lau");
const jane = Person.fromJSON({ firstName: "leo", lastName: "lau" });
john.introduceYourselfTo('jack');
这个版本的class
是大多数js
开发人员今天熟悉的。它是语言的第一组坚实的类特征,但有几个东西缺少。缺少的特性之一是字段的语法。在随后版本的语言中,通过启用公共字段和公共静态字段语法来解决这个问题。这里有一个示例:
class Person {
age = 0; // 字段语法
static typeName = "Person"; // 公共字段的语法
shareYourAge() {
console.log(`I am ${this.age} years old.`);
}
}
const john = new Person("leo", "lau");
leo.age = 25;
leo.shareYourAge();
在设计装饰器的同时,还开发了一个自动访问器的配套功能。使用新的accessor
关键词即可:
class Person {
accessor age = 0;
}
const john = new Person("leo", "lau");
leo.age = 25;
leo.shareYourAge();
2021年7月,主流浏览器都支持private class
,受保护的私有功能带到了语言中。私有成员都是通过#
字符开头的,只能通过静态调用,下面是一个示例:
class Person {
#firstName; // 私有属性
#lastName; // 私有属性
constructor(firstName, lastName) {
this.#firstName = firstName;
this.#lastName = lastName;
}
get firstName() { return this.#firstName; }
get lastName() { return this.#lastName; }
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
introduceYourselfTo(other) {
const name = other.firstName ?? other;
console.log(`Hello ${name}! My name is ${this.fullName}.`);
}
static fromJSON(json) {
return new Person(json.firstName, json.lastName);
}
}
通过将私有字段与公共getter
相结合,js
现在也支持上面所示的受平台保护的只读属性。
字段、getters
、setters
和方法、实例和静态都可以私有化,但是构造函数不能。但是可以使用一种模式来防止对构造函数进行未经授权的调用。
class SomeSingletonService {
static #key = {};
static instance = new SomeSingletonService(this.#key);
constructor(key) {
if (key !== SomeSingletonService.#key) {
throw new TypeError("SomeSingletonService is not constructable.");
}
}
}
这个模式中的重要细节是我们持有一个私有静态键,它是构造函数的第一个参数。如果没有提供该键来解锁构造函数,则抛出异常。现在我们的类可以使用它的私钥随意创建实例,但是试图通过构造函数实例化的任何其他方都被阻塞了。
虽然JavaScript
并没有提供这种特性,但是我们可以使用一些方式来处理私有构造函数的方法。以下是它的工作原理:
// 父类:
function Question(key) {
return class {
#answer = 42;
answer(shareKey) {
if (shareKey === key) {
return this.#answer;
}
throw new TypeError("Access Denied");
}
}
}
const key = {};
// 子类
class DeepThought extends Question(key) {
get #answer() {
return this.answer(key);
}
tellMeTheAnswer() {
console.log(this.#answer);
}
}
const dm = new DeepThought();
dm.tellMeTheAnswer();
我们首先创建一个工厂函数Question
,这个函数以一个键作为输入,它将使用一个键检查对它提供的任何共享值的访问。然后,我们创建一个唯一的键,并将它传递给超类工厂,作为子类定义的一部分。在子类中,我们创建了一个用于访问共享值的私有属性,该属性使用预先定义的密钥在内部调用超类共享方法。为了避免将受保护的成员变成公共成员,我们也让我们的子类getter
私有化。
上述代码用于受保护的场景以及任意共享,仅通过共享密钥即可。然而,有一些模板代码并没有特别的表现力,甚至有点让人迷惑。我们可以通过使用 装饰器:
function Question(share) {
return class {
@share accessor #answer = 42;
}
}
class DeepThought extends Question(share) {
@access accessor #answer;
tellMeTheAnswer() {
console.log(this.#answer);
}
}
const dm = new DeepThought();
dm.tellMeTheAnswer();
使用装饰器能更清楚什么是超类共享的值和什么是子类访问的值。它还消除了前一个版本中由相似名称引起的一些混淆,并且更适合于多个成员。
实现这一目的的技术是为两个共享公共私有访问器存储的装饰器使用工厂函数。