static#(プライベート)get(ゲッター)とset(セッター)*(ジェネレーター)asyncpublic、private、またはprotectedにするこの章では、TypeScriptでのクラス定義の仕組みを検証します。
このセクションは、プレーンなJavaScriptでのクラス定義のチートシートです。
class OtherClass {}
class MyClass1 extends OtherClass {
publicInstanceField = 1;
constructor() {
super();
}
publicPrototypeMethod() {
return 2;
}
}
const inst1 = new MyClass1();
assert.equal(inst1.publicInstanceField, 1);
assert.equal(inst1.publicPrototypeMethod(), 2); 次のセクションでは、修飾子について説明します
最後に、修飾子を組み合わせる方法を示す表があります。
staticclass MyClass2 {
static staticPublicField = 1;
static staticPublicMethod() {
return 2;
}
}
assert.equal(MyClass2.staticPublicField, 1);
assert.equal(MyClass2.staticPublicMethod(), 2);#(プライベート)class MyClass3 {
#privateField = 1;
#privateMethod() {
return 2;
}
static accessPrivateMembers() {
// Private members can only be accessed from inside class definitions
const inst3 = new MyClass3();
assert.equal(inst3.#privateField, 1);
assert.equal(inst3.#privateMethod(), 2);
}
}
MyClass3.accessPrivateMembers();JavaScriptに関する警告
TypeScriptはバージョン3.8からプライベートフィールドをサポートしていますが、現在プライベートメソッドはサポートしていません。
get(ゲッター)とset(セッター)大まかに言うと、アクセサーはプロパティにアクセスすることによって呼び出されるメソッドです。アクセサーには、ゲッターとセッターの2種類があります。
class MyClass5 {
#name = 'Rumpelstiltskin';
/** Prototype getter */
get name() {
return this.#name;
}
/** Prototype setter */
set name(value) {
this.#name = value;
}
}
const inst5 = new MyClass5();
assert.equal(inst5.name, 'Rumpelstiltskin'); // getter
inst5.name = 'Queen'; // setter
assert.equal(inst5.name, 'Queen'); // getter*(ジェネレーター)class MyClass6 {
* publicPrototypeGeneratorMethod() {
yield 'hello';
yield 'world';
}
}
const inst6 = new MyClass6();
assert.deepEqual(
[...inst6.publicPrototypeGeneratorMethod()],
['hello', 'world']);asyncclass MyClass7 {
async publicPrototypeAsyncMethod() {
const result = await Promise.resolve('abc');
return result + result;
}
}
const inst7 = new MyClass7();
inst7.publicPrototypeAsyncMethod()
.then(result => assert.equal(result, 'abcabc'));const publicInstanceFieldKey = Symbol('publicInstanceFieldKey');
const publicPrototypeMethodKey = Symbol('publicPrototypeMethodKey');
class MyClass8 {
[publicInstanceFieldKey] = 1;
[publicPrototypeMethodKey]() {
return 2;
}
}
const inst8 = new MyClass8();
assert.equal(inst8[publicInstanceFieldKey], 1);
assert.equal(inst8[publicPrototypeMethodKey](), 2);コメント
Symbol.iteratorなどのシンボルです。ただし、角かっこ内では任意の式を使用できます。フィールド(レベルがない場合は、インスタンスレベルで構成が存在することを意味します)
| レベル | 可視性 |
|---|---|
| (インスタンス) | |
| (インスタンス) | # |
static |
|
static |
# |
メソッド(レベルがない場合は、プロトタイプレベルで構成が存在することを意味します)
| レベル | アクセサー | 非同期 | ジェネレーター | 可視性 |
|---|---|---|---|---|
| (プロトタイプ) | ||||
| (プロトタイプ) | get |
|||
| (プロトタイプ) | set |
|||
| (プロトタイプ) | async |
|||
| (プロトタイプ) | * |
|||
| (プロトタイプ) | async |
* |
||
| (プロトタイプ関連) | # |
|||
| (プロトタイプ関連) | get |
# |
||
| (プロトタイプ関連) | set |
# |
||
| (プロトタイプ関連) | async |
# |
||
| (プロトタイプ関連) | * |
# |
||
| (プロトタイプ関連) | async |
* |
# |
|
static |
||||
static |
get |
|||
static |
set |
|||
static |
async |
|||
static |
* |
|||
static |
async |
* |
||
static |
# |
|||
static |
get |
# |
||
static |
set |
# |
||
static |
async |
# |
||
static |
* |
# |
||
static |
async |
* |
# |
メソッドの制限
クラスでは、プロトタイプオブジェクトのチェーンが2つあることを念頭に置いておくことが重要です。
次のプレーンなJavaScriptの例を考えてみましょう
class ClassA {
static staticMthdA() {}
constructor(instPropA) {
this.instPropA = instPropA;
}
prototypeMthdA() {}
}
class ClassB extends ClassA {
static staticMthdB() {}
constructor(instPropA, instPropB) {
super(instPropA);
this.instPropB = instPropB;
}
prototypeMthdB() {}
}
const instB = new ClassB(0, 1);図1は、ClassAおよびClassBによって作成されるプロトタイプチェーンがどのようなものかを示しています。
デフォルトでは、TypeScriptのすべてのデータスロットはパブリックプロパティです。データを非公開にするには、次の2つの方法があります。
次に両方を見ていきます。
TypeScriptは現在、プライベートメソッドをサポートしていないことに注意してください。
プライベートプロパティはTypeScriptのみ(静的)の機能です。任意のプロパティにキーワードprivateを付けることでプライベートにすることができます(A行)。
class PersonPrivateProperty {
private name: string; // (A)
constructor(name: string) {
this.name = name;
}
sayHello() {
return `Hello ${this.name}!`;
}
}これで、間違ったスコープでそのプロパティにアクセスすると、コンパイル時エラーが発生します(A行)。
const john = new PersonPrivateProperty('John');
assert.equal(
john.sayHello(), 'Hello John!');
// @ts-expect-error: Property 'name' is private and only accessible
// within class 'PersonPrivateProperty'. (2341)
john.name; // (A)ただし、privateは実行時には何も変更しません。そこでは、プロパティ.nameはパブリックプロパティと区別できません。
assert.deepEqual(
Object.keys(john),
['name']);また、クラスがコンパイルされるJavaScriptコードを見ると、プライベートプロパティが実行時に保護されていないことがわかります。
class PersonPrivateProperty {
constructor(name) {
this.name = name;
}
sayHello() {
return `Hello ${this.name}!`;
}
}プライベートフィールドは、TypeScriptがバージョン3.8からサポートしている新しいJavaScript機能です。
class PersonPrivateField {
#name: string;
constructor(name: string) {
this.#name = name;
}
sayHello() {
return `Hello ${this.#name}!`;
}
}このバージョンのPersonは、ほとんどの場合、プライベートプロパティバージョンと同じように使用されます。
const john = new PersonPrivateField('John');
assert.equal(
john.sayHello(), 'Hello John!');ただし、今回はデータが完全にカプセル化されています。クラス外でプライベートフィールド構文を使用すると、JavaScriptの構文エラーになります。そのため、このコードを実行できるように、A行でeval()を使用する必要があります。
assert.throws(
() => eval('john.#name'), // (A)
{
name: 'SyntaxError',
message: "Private field '#name' must be declared in "
+ "an enclosing class",
});
assert.deepEqual(
Object.keys(john),
[]);コンパイル結果は、以前よりもはるかに複雑になりました(少し簡略化されています)。
var __classPrivateFieldSet = function (receiver, privateMap, value) {
if (!privateMap.has(receiver)) {
throw new TypeError(
'attempted to set private field on non-instance');
}
privateMap.set(receiver, value);
return value;
};
// Omitted: __classPrivateFieldGet
var _name = new WeakMap();
class Person {
constructor(name) {
// Add an entry for this instance to _name
_name.set(this, void 0);
// Now we can use the helper function:
__classPrivateFieldSet(this, _name, name);
}
// ···
}このコードは、インスタンスデータを非公開にするための一般的な手法を使用しています。
このトピックの詳細については、「JavaScript for impatient programmers」を参照してください。
プライベートフィールドとプライベートプロパティはサブクラスではアクセスできません(A行)。
class PrivatePerson {
private name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
return `Hello ${this.name}!`;
}
}
class PrivateEmployee extends PrivatePerson {
private company: string;
constructor(name: string, company: string) {
super(name);
this.company = company;
}
sayHello() {
// @ts-expect-error: Property 'name' is private and only
// accessible within class 'PrivatePerson'. (2341)
return `Hello ${this.name} from ${this.company}!`; // (A)
}
}A行でprivateからprotectedに切り替えることで、前の例を修正できます(整合性を保つためにB行でも切り替えます)。
class ProtectedPerson {
protected name: string; // (A)
constructor(name: string) {
this.name = name;
}
sayHello() {
return `Hello ${this.name}!`;
}
}
class ProtectedEmployee extends ProtectedPerson {
protected company: string; // (B)
constructor(name: string, company: string) {
super(name);
this.company = company;
}
sayHello() {
return `Hello ${this.name} from ${this.company}!`; // OK
}
}コンストラクターもプライベートにすることができます。これは、静的ファクトリメソッドがあり、クライアントに常にそれらのメソッドを使用させ、コンストラクターを直接使用させたくない場合に役立ちます。静的メソッドはプライベートクラスメンバーにアクセスできるため、ファクトリメソッドは引き続きコンストラクターを使用できます。
次のコードでは、静的ファクトリメソッドDataContainer.create()が1つあります。これは、非同期でロードされたデータを介してインスタンスを設定します。非同期コードをファクトリメソッドに保持すると、実際のクラスを完全に同期的にすることができます。
class DataContainer {
#data: string;
static async create() {
const data = await Promise.resolve('downloaded'); // (A)
return new this(data);
}
private constructor(data: string) {
this.#data = data;
}
getData() {
return 'DATA: '+this.#data;
}
}
DataContainer.create()
.then(dc => assert.equal(
dc.getData(), 'DATA: downloaded'));実際のコードでは、A行で非同期的にデータをロードするためにfetch()または同様のPromiseベースのAPIを使用します。
プライベートコンストラクターは、DataContainerがサブクラス化されるのを防ぎます。サブクラスを許可する場合は、protectedにする必要があります。
コンパイラ設定--strictPropertyInitializationがオンになっている場合(--strictを使用する場合はそうなる)、TypeScriptは宣言されたすべてのインスタンスプロパティが正しく初期化されているかどうかを確認します。
コンストラクターでの代入によって
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}またはプロパティ宣言の初期化子を介して
class Point {
x = 0;
y = 0;
// No constructor needed
}ただし、TypeScriptが認識しない方法でプロパティを初期化することがあります。その場合は、感嘆符(definite assignment assertions)を使用して、TypeScriptの警告をオフにすることができます(A行とB行)。
class Point {
x!: number; // (A)
y!: number; // (B)
constructor() {
this.initProperties();
}
initProperties() {
this.x = 0;
this.y = 0;
}
}次の例では、definite assignment assertionsも必要です。ここでは、コンストラクターパラメータpropsを介してインスタンスプロパティを設定します。
class CompilerError implements CompilerErrorProps { // (A)
line!: number;
description!: string;
constructor(props: CompilerErrorProps) {
Object.assign(this, props); // (B)
}
}
// Helper interface for the parameter properties
interface CompilerErrorProps {
line: number,
description: string,
}
// Using the class:
const err = new CompilerError({
line: 123,
description: 'Unexpected token',
});注
Object.assign()を使用して、パラメータpropsのプロパティをthisにコピーします。implementsによって、クラスがインターフェイスCompilerErrorPropsの一部であるすべてのプロパティを宣言することが保証されます。public、private、またはprotectedにするコンストラクターパラメータにキーワードpublicを使用すると、TypeScriptは2つのことを行います。
したがって、次の2つのクラスは同等です。
class Point1 {
constructor(public x: number, public y: number) {
}
}
class Point2 {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}publicの代わりにprivateまたはprotectedを使用すると、対応するインスタンスプロパティはプライベートまたはプロテクトになります(パブリックではありません)。
TypeScriptでは、2つの構成要素を抽象にすることができます。
次のコードは、抽象クラスと抽象メソッドを示しています。
一方では、抽象スーパークラスPrintableとそのヘルパークラスStringBuilderがあります。
class StringBuilder {
string = '';
add(str: string) {
this.string += str;
}
}
abstract class Printable {
toString() {
const out = new StringBuilder();
this.print(out);
return out.string;
}
abstract print(out: StringBuilder): void;
}他方では、具象サブクラスEntriesとEntryがあります。
class Entries extends Printable {
entries: Entry[];
constructor(entries: Entry[]) {
super();
this.entries = entries;
}
print(out: StringBuilder): void {
for (const entry of this.entries) {
entry.print(out);
}
}
}
class Entry extends Printable {
key: string;
value: string;
constructor(key: string, value: string) {
super();
this.key = key;
this.value = value;
}
print(out: StringBuilder): void {
out.add(this.key);
out.add(': ');
out.add(this.value);
out.add('\n');
}
}最後に、これがEntriesとEntryを使用している私たちのものです。
const entries = new Entries([
new Entry('accept-ranges', 'bytes'),
new Entry('content-length', '6518'),
]);
assert.equal(
entries.toString(),
'accept-ranges: bytes\ncontent-length: 6518\n');抽象クラスに関するメモ