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