diff --git a/files/ja/web/javascript/reference/classes/constructor/index.md b/files/ja/web/javascript/reference/classes/constructor/index.md index b64af9a21c8502..6c88c6862648ec 100644 --- a/files/ja/web/javascript/reference/classes/constructor/index.md +++ b/files/ja/web/javascript/reference/classes/constructor/index.md @@ -1,23 +1,35 @@ --- -title: コンストラクター +title: constructor slug: Web/JavaScript/Reference/Classes/constructor +l10n: + sourceCommit: 41cddfdaeed4a73fb8234c332150df8e54df31e9 --- {{jsSidebar("Classes")}} -`constructor` メソッドは、 {{jsxref("Statements/class", "class")}} で作成されたオブジェクトの生成と初期化のための特殊なメソッドです。 +**`constructor`** メソッドは、[クラス](/ja/docs/Web/JavaScript/Reference/Classes)で作成されたオブジェクトインスタンスの生成と初期化を行うための特殊なメソッドです。 + +> **メモ:** このページでは `constructor` の構文を紹介します。すべてのオブジェクトに存在する `constructor` プロパティについては、 {{jsxref("Object.prototype.constructor")}} を参照してください。 {{EmbedInteractiveExample("pages/js/classes-constructor.html")}} ## 構文 +```js-nolint +constructor() { /* … */ } +constructor(argument0) { /* … */ } +constructor(argument0, argument1) { /* … */ } +constructor(argument0, argument1, /* …, */ argumentN) { /* … */ } ``` -constructor([arguments]) { ... } -``` + +追加の構文上の制約があります。 + +- `constructor` という名前のクラスメソッドは、[ゲッター](/ja/docs/Web/JavaScript/Reference/Functions/get)、[セッター](/ja/docs/Web/JavaScript/Reference/Functions/set)、[非同期](/ja/docs/Web/JavaScript/Reference/Statements/async_function)、[ジェネレーター](/ja/docs/Web/JavaScript/Reference/Statements/function*)メソッドになることはできません。 +- 1 つのクラスが複数の `constructor` メソッドを持つことはできません。 ## 解説 -コンストラクターを使用すると、インスタンス化されたオブジェクトに対して、他のメソッドを呼び出す前に行う必要のある独自の初期化を提供することができます。 +コンストラクターを使用すると、インスタンス化されたオブジェクトに対して、他のメソッドを呼び出す前に行う必要のある独自の初期化を行うことができます。 ```js class Person { @@ -26,29 +38,31 @@ class Person { } introduce() { - console.log(`Hello, my name is ${this.name}`); + console.log(`こんにちは、私は${this.name}です。`); } } -const otto = new Person("Otto"); +const otto = new Person("オットー"); -otto.introduce(); +otto.introduce(); // こんにちは、私はオットーです。 ``` 独自のコンストラクターを提供しなかった場合は、既定のコンストラクターが提供されます。クラスが基底クラスである場合、既定のコンストラクターは空です。 -```js +```js-nolint constructor() {} ``` クラスが派生クラスの場合、既定のコンストラクターが親コンストラクターを呼び出し、与えられた引数を渡します。 -```js +```js-nolint constructor(...args) { super(...args); } ``` +> **メモ:** 上記のような明示的なコンストラクターと既定のコンストラクターの異なる点は、後者が実際には[配列イテレーター](/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/@@iterator)を呼び出して[引数のスプレッド](/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax)を行わないことです。 + それがこのようなコードを動作させることができます。 ```js @@ -62,7 +76,7 @@ try { throw new ValidationError("Not a valid phone number"); } catch (error) { if (error instanceof ValidationError) { - console.log(error.name); // This is Error instead of ValidationError! + console.log(error.name); // これは ValidationError の代わりのエラー console.log(error.printCustomerMessage()); } else { console.log("Unknown error", error); @@ -71,37 +85,120 @@ try { } ``` -`ValidationError` クラスは、独自の初期化を行う必要がないため、明示的なコンストラクターは必要ありません。既定のコンストラクターは、与えられた引数から親の `Error` の初期化を行います。 +`ValidationError` クラスは、独自の初期化を行う必要がないため、明示的なコンストラクターは必要ありません。 +既定のコンストラクターは、与えられた引数から親の `Error` の初期化を行います。 -ただし、独自のコンストラクターを提供し、クラスが親クラスから派生している場合は、 `super` を使用して親クラスのコンストラクターを明示的に呼び出す必要があります。例えば、以下のようになります。 +ただし、独自のコンストラクターを提供し、クラスが親クラスから派生している場合は、 [`super()`](/ja/docs/Web/JavaScript/Reference/Operators/super) を使用して親クラスのコンストラクターを明示的に呼び出す必要があります。 +例えば、以下のようになります。 ```js class ValidationError extends Error { constructor(message) { - super(message); // call parent class constructor + super(message); // 親クラスのコンストラクターの呼び出し this.name = "ValidationError"; this.code = "42"; } printCustomerMessage() { - return `Validation failed :-( (details: ${this.message}, code: ${this.code})`; + return `検証に失敗しました :-( (details: ${this.message}, code: ${this.code})`; } } try { - throw new ValidationError("Not a valid phone number"); + throw new ValidationError("正しい電話番号ではありません。"); } catch (error) { if (error instanceof ValidationError) { - console.log(error.name); // Now this is ValidationError! + console.log(error.name); // これは ValidationError になる console.log(error.printCustomerMessage()); } else { - console.log("Unknown error", error); + console.log("未知のエラーです", error); throw error; } } ``` -クラスには "`constructor`" という名前の特別なメソッドが 1 つだけ存在します。クラス内に複数の `constructor` メソッドが存在すると、 {{jsxref("SyntaxError")}} エラーが発生します。 +クラスで [`new`](/ja/docs/Web/JavaScript/Reference/Operators/new) を使用すると、以下の段階を踏みます。 + +1. (派生クラスの場合) `super()` 呼び出しが評価される前の `constructor` 本体。この部分はまだ初期化されていないので、 `this` にアクセスしてはいけません。 +2. (派生クラスの場合) `super()` 呼び出しが評価され、同じ処理で親クラスが初期化されます。 +3. 現在のクラスの[フィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields)が初期化されます。 +4. `super()` 呼び出し後の `constructor` 本体(基底クラスの場合は本体全体)が評価されます。 + +`constructor` 本体の中では、 [`this`](/ja/docs/Web/JavaScript/Reference/Operators/this) で作成されるオブジェクトにアクセスしたり [`new`](/ja/docs/Web/JavaScript/Reference/Operators/new) で呼び出されるクラスに [`new.target`](/ja/docs/Web/JavaScript/Reference/Operators/new) でアクセスしたりすることができます。メソッド([ゲッター](/ja/docs/Web/JavaScript/Reference/Functions/get)、[セッター](/ja/docs/Web/JavaScript/Reference/Functions/set)を含む)と[プロトタイプチェーン](/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) は `constructor` が実行される前に `this` で初期化されているので、スーパークラスのコンストラクターからサブクラスのメソッドにアクセスすることもできることに注意してください。しかし、これらのメソッドが `this` を使用している場合、 `this` はまだ完全に初期化されていません。これは、派生クラスのパブリックフィールドを読むと `undefined` になり、プライベートフィールドを読むと `TypeError` になるということです。 + +```js example-bad +new (class C extends class B { + constructor() { + console.log(this.foo()); + } +} { + #a = 1; + foo() { + return this.#a; // TypeError: Cannot read private member #a from an object whose class did not declare it + // これは、クラスが宣言していないのではなく、スーパークラスの + // コンストラクターが実行されている時点で、プライベートフィールドが + // まだ初期化されていないため。 + } +})(); +``` + +`constructor` メソッドは返値を持つことができます。基底クラスはコンストラクターから何らかの値を返すことができますが、派生クラスはオブジェクトまたは `undefined` を返すか、 {{jsxref("TypeError")}} を発生させなければなりません。 + +```js +class ParentClass { + constructor() { + return 1; + } +} + +console.log(new ParentClass()); // ParentClass {} +// 返値はオブジェクトではないので無視される。 これはコンストラクター関数と同じ。 + +class ChildClass extends ParentClass { + constructor() { + return 1; + } +} + +console.log(new ChildClass()); // TypeError: Derived constructors may only return object or undefined +``` + +親クラスのコンストラクターがオブジェクトを返した場合、そのオブジェクトは派生クラスの[クラスフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields)を定義する際の値として使用します。このトリックは[「返値の上書き」](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties#オーバーライドしたオブジェクトの返却)と呼ばれ、派生クラスのフィールド([プライベート](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties)なものも含む)を無関係なオブジェクトに定義することができます。 + +`constructor` は通常の[メソッド](/ja/docs/Web/JavaScript/Reference/Functions/Method_definitions)構文に従うので、[デフォルト引数](/ja/docs/Web/JavaScript/Reference/Functions/Default_parameters)や[残余引数](/ja/docs/Web/JavaScript/Reference/Functions/rest_parameters)などをすべて使用することができます。 + +```js +class Person { + constructor(name = "名無し") { + this.name = name; + } + introduce() { + console.log(`こんにちは、私は${this.name}`); + } +} + +const person = new Person(); +person.introduce(); // こんにちは、私は名無し +``` + +コンストラクターはリテラル名でなければなりません。計算されたプロパティ名はコンストラクターにはなれません。 + +```js +class Foo { + // これは計算プロパティ名です。コンストラクターとしてピックアップされることはありません。 + ["constructor"]() { + console.log("called"); + this.a = 1; + } +} + +const foo = new Foo(); // ログ出力なし +console.log(foo); // Foo {} +foo.constructor(); // "called" と出力 +console.log(foo); // Foo { a: 1 } +``` + +非同期メソッド、ジェネレータメソッド、アクセサ、クラスフィールドは `constructor` と名付けることは禁止されています。プライベートな名前を `#constructor` と呼び出すことはできません。 `constructor` という名前のメンバーはプレーンなメソッドでなければなりません。 ## 例 @@ -112,11 +209,11 @@ try { ```js class Square extends Polygon { constructor(length) { - // Here, it calls the parent class' constructor with lengths - // provided for the Polygon's width and height + // ここでは、ポリゴンの幅と高さを指定された長さにして、親クラスの + // コンストラクターを呼び出しています。 super(length, length); - // NOTE: In derived classes, `super()` must be called before you - // can use `this`. Leaving this out will cause a ReferenceError. + // メモ: 派生クラスでは、`this` を使用する前に `super()` を呼び出す + // 必要があります。これを省略すると ReferenceError が発生します。 this.name = "Square"; } @@ -131,9 +228,9 @@ class Square extends Polygon { } ``` -### 他の例 +### 異なるプロトタイプにバインドされたコンストラクターでの super を呼び出し -ここでは、 `Square` クラスのプロトタイプが変更されていますが、新しいインスタンスが作成されたときには、その基底クラスである `Polygon` のコンストラクターが呼び出されます。 +`super()` は現在のクラスのプロトタイプであるコンストラクターを呼び出します。現在のクラスのプロトタイプを変更した場合、 `super()` は新しいプロトタイプのコンストラクターを呼び出します。現在のクラスの `prototype` プロパティを変更しても、 `super()` が呼び出すコンストラクターには影響しません。 ```js class Polygon { @@ -142,21 +239,33 @@ class Polygon { } } +class Rectangle { + constructor() { + this.name = "Rectangle"; + } +} + class Square extends Polygon { constructor() { super(); } } -class Rectangle {} +// Polygon の代わりに(基本クラスである) Rectangle を継承するようにする +Object.setPrototypeOf(Square, Rectangle); -Object.setPrototypeOf(Square.prototype, Rectangle.prototype); +const newInstance = new Square(); -console.log(Object.getPrototypeOf(Square.prototype) === Polygon.prototype); //false -console.log(Object.getPrototypeOf(Square.prototype) === Rectangle.prototype); //true +// newInstance はまだ Polygon のインスタンスです。 +// Square.prototype のプロトタイプを変更していないので、 +// newInstance のプロトタイプチェーンは以下のままです。 +// newInstance --> Square.prototype --> Polygon.prototype +console.log(newInstance instanceof Polygon); // true +console.log(newInstance instanceof Rectangle); // false -let newInstance = new Square(); -console.log(newInstance.name); //Polygon +// ただし、 super() はコンストラクターとして Rectangle を呼び出すため、 +// newInstance の name プロパティは Rectangle のロジックで初期化されます。 +console.log(newInstance.name); // Rectangle ``` ## 仕様書 @@ -169,8 +278,9 @@ console.log(newInstance.name); //Polygon ## 関連情報 +- [クラスの使用](/ja/docs/Web/JavaScript/Guide/Using_classes)ガイド +- [クラス](/ja/docs/Web/JavaScript/Reference/Classes) +- [静的初期化ブロック](/ja/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks) +- {{jsxref("Statements/class", "class")}} - {{jsxref("Operators/super", "super()")}} -- {{jsxref("Statements/class", "クラス宣言", "", "true")}} -- {{jsxref("Operators/class", "クラス式", "", "true")}} -- {{jsxref("Classes")}} -- [Object.prototype.constructor](/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor) +- {{jsxref("Object.prototype.constructor")}} diff --git a/files/ja/web/javascript/reference/classes/extends/index.md b/files/ja/web/javascript/reference/classes/extends/index.md index bc9873b1dc76ed..28a6ad3b43ea60 100644 --- a/files/ja/web/javascript/reference/classes/extends/index.md +++ b/files/ja/web/javascript/reference/classes/extends/index.md @@ -1,31 +1,182 @@ --- title: extends slug: Web/JavaScript/Reference/Classes/extends +l10n: + sourceCommit: 41cddfdaeed4a73fb8234c332150df8e54df31e9 --- {{jsSidebar("Classes")}} -**`extends`** キーワードは[クラス宣言](/ja/docs/Web/JavaScript/Reference/Statements/class)や[クラス式](/ja/docs/Web/JavaScript/Reference/Operators/class)の中で、他のクラスの子であるクラスを生成するために使用します。 +**`extends`** キーワードは、[クラス宣言](/ja/docs/Web/JavaScript/Reference/Statements/class)や[クラス式](/ja/docs/Web/JavaScript/Reference/Operators/class)の中で、他のクラスの子であるクラスを生成するために使用します。 -{{EmbedInteractiveExample("pages/js/classes-extends.html")}} +{{EmbedInteractiveExample("pages/js/classes-extends.html", "taller")}} ## 構文 +```js-nolint +class ChildClass extends ParentClass { /* … */ } ``` -class ChildClass extends ParentClass { ... } -``` + +- `ParentClass` + - : コンストラクター関数(クラスを含む)または `null` と評価される式。 ## 解説 -`extends` キーワードは、独自のクラスや組込みオブジェクトをサブクラス化するために使用することができます。 +`extends` キーワードは、組み込みオブジェクトと同様にカスタムクラスをサブクラス化するために使用することができます。 + +[`new`](/ja/docs/Web/JavaScript/Reference/Operators/new) で呼び出すことができ、 [`prototype`](/ja/docs/Web/JavaScript/Reference/Global_Objects/Function) プロパティを持つコンストラクターであれば、親クラスの候補になることができます。例えば、[バインド済み関数](/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)や {{jsxref("Proxy")}} は構築可能ですが、これらは `prototype` プロパティを持たないので、サブクラス化できません。 + +```js +function OldStyleClass() { + this.someProperty = 1; +} +OldStyleClass.prototype.someMethod = function () {}; + +class ChildClass extends OldStyleClass {} + +class ModernClass { + someProperty = 1; + someMethod() {} +} + +class AnotherChildClass extends ModernClass {} +``` + +`ParentClass`の `prototype` プロパティは {{jsxref("Object")}} または [`null`](/ja/docs/Web/JavaScript/Reference/Operators/null) でなければなりませんが、オブジェクトでない `prototype` は本来の動作をしないので、実際にはほとんど気にすることはないでしょう。([`new`](/ja/docs/Web/JavaScript/Reference/Operators/new) 演算子では無視されます。) + +```js +function ParentClass() {} +ParentClass.prototype = 3; + +class ChildClass extends ParentClass {} +// Uncaught TypeError: Class extends value does not have valid prototype property 3 + +console.log(Object.getPrototypeOf(new ParentClass())); +// [Object: null prototype] {} +// Not actually a number! +``` + +`extends` は `ChildClass` と `ChildClass.prototype` の両方のプロトタイプを設定します。 -拡張したものの `.prototype` は、{{jsxref("Object")}} か {{jsxref("null")}} である必要があります。 +| | `ChildClass` のプロトタイプ | `ChildClass.prototype` のプロトタイプ | +| --------------------------------- | --------------------------- | ------------------------------------- | +| `extends` 節がない | `Function.prototype` | `Object.prototype` | +| [`extends null`](#extending_null) | `Function.prototype` | `null` | +| `extends ParentClass` | `ParentClass` | `ParentClass.prototype` | + +```js +class ParentClass {} +class ChildClass extends ParentClass {} + +// 静的プロパティの継承が可能 +Object.getPrototypeOf(ChildClass) === ParentClass; +// インスタンスプロパティの継承が可能 +Object.getPrototypeOf(ChildClass.prototype) === ParentClass.prototype; +``` + +`extends` の右辺は識別子である必要はありません。コンストラクターとして評価される式なら何でも使用することができます。これは[ミックスイン](#ミックスイン)を作成するのに有益なことが多いです。 `extends` 式の `this` 値はクラス定義の外側の `this` であり、このクラスは初期化されていないので、クラス名を参照すると {{jsxref("ReferenceError")}} になります。この式では {{jsxref("Operators/await", "await")}} および {{jsxref("Operators/yield", "yield")}} は期待通りに動作します。 + +```js +class SomeClass extends class { + constructor() { + console.log("基底クラス"); + } +} { + constructor() { + super(); + console.log("派生クラス"); + } +} + +new SomeClass(); +// 基底クラス +// 派生クラス +``` + +基底クラスはコンストラクターから何らかのものを返すことができますが、派生クラスはオブジェクトを返すか `undefined` を返さなければなりません。 + +```js +class ParentClass { + constructor() { + return 1; + } +} + +console.log(new ParentClass()); // ParentClass {} +// オブジェクトではないため、返値は無視される +// これは関数コンストラクターと整合する + +class ChildClass extends ParentClass { + constructor() { + super(); + return 1; + } +} + +console.log(new ChildClass()); // TypeError: Derived constructors may only return object or undefined +``` + +親クラスのコンストラクターがオブジェクトを返す場合、[クラスフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields)をさらに初期化するときに、そのオブジェクトが派生クラスの `this` 値として使用されます。このトリックは[「返値の上書き」](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties#オーバーライドしたオブジェクトの返却)と呼ばれ、派生クラスのフィールド([プライベートフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties)を含む)を無関係なオブジェクトに定義することができます。 + +### 組み込みクラスのサブクラス化 + +> **警告:** 標準化委員会は、これまでの仕様にあった組み込みクラスにおけるサブクラス化メカニズムは過剰に設計されており、パフォーマンスとセキュリティへの無視できない影響を発生させているという見解を固めました。新しい組み込みメソッドはサブクラスについてあまり考慮されておらず、エンジンの実装者は[一部のサブクラス化メカニズムを除去するかどうか調査しています](https://github.com/tc39/proposal-rm-builtin-subclassing)。組み込みクラスを拡張する際には、継承の代わりにコンポジションを使用することを検討してください。 + +クラスを拡張する際に期待されることをいくつか示します。 + +- 静的ファクトリーメソッド({{jsxref("Promise.resolve()")}} や {{jsxref("Array.from()")}})をサブクラスで呼び出した場合、返されるインスタンスが常にサブクラスのインスタンスになること。 +- 新しいインスタンスを返すインスタンスメソッド({{jsxref("Promise.prototype.then()")}} や {{jsxref("Array.prototype.map()")}} など)をサブクラスで呼び出した場合、返されるインスタンスは常にサブクラスのインスタンスになること。 +- インスタンスメソッドの移譲先は、可能な限り最小限の基本的なメソッドの集合になること。例えば、 {{jsxref("Promise")}} のサブクラスの場合、 {{jsxref("Promise/then", "then()")}} をオーバーライドすると、自動的に {{jsxref("Promise/catch", "catch()")}} の動作が変化すること。または、 {{jsxref("Map")}} のサブクラスの場合、 {{jsxref("Map/set", "set()")}} をオーバーライドすると、 {{jsxref("Map/Map", "Map()")}} コンストラクターの動作が自動的に変更さえること。 + +しかし、上記のような期待を適切に実装するには、只ならぬ努力が必要です。 + +- まず、返されるインスタンスを構築するコンストラクターを取得するために、静的メソッドで [`this`](/ja/docs/Web/JavaScript/Reference/Operators/this) の値を読み取ることが要求されることです。これは `[p1, p2, p3].map(Promise.resolve)` が `Promise.resolve` 内の `this` が未定義であるためにエラーを発生することになります。これを修正する方法としては、 {{jsxref("Array.from()")}} がそうであるように、 `this` がコンストラクターでない場合に基底クラスで代替処理をすることですが、それでも基底クラスが特殊ケースであることを意味しています。 +- 2 つ目は、インスタンスメソッドがコンストラクター関数を取得するために [`this.constructor`](/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor) を読み込むことを要求されることです。しかし、コンストラクターのプロパティは書き込みと設定の両方が可能であり、保護されていないため、 `new this.constructor()` は古いコードを壊す可能性があります。そのため、組み込みメソッドをコピーする場合の多くは、代わりにコンストラクターの [`@@species`](/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species) プロパティを使用しています(既定では、単にコンストラクター自身である `this` を返します)。しかし、 `@@species` によって任意のコードを実行したり、任意のタイプのインスタンスを作成したりすることができるため、セキュリティ上の問題があり、サブクラスの意味づけが非常に複雑になります。 +- 3 つ目はカスタムコードを目に見える形で呼び出すことになり、多くのオプティマイザーの実装が難しくなります。例えば、 `Map()` コンストラクターが _x_ 要素の反復可能オブジェクトで呼び出された場合、内部ストレージに要素をコピーするだけではなく、目に見える形で `set()` メソッドを _x_ 回呼び出さなければなりません。 + +これらの問題は組み込みクラスに固有のものではありません。自分自身で作成したクラスについても、同じような決定をしなければならないことがあるでしょう。しかし、組み込みクラスでは、最適化とセキュリティがより大きな関心事です。新しい組み込みメソッドは常に基底クラスを構築し、可能な限りいくつかのカスタムメソッドを呼び出します。上記の期待値を達成しながら組み込みクラスをサブクラス化したい場合は、既定値の動作が組み込まれているメソッドをすべてオーバーライドする必要があります。既定では継承されているため、基底クラスに新しいメソッドを追加すると、サブクラスの意味づけが崩れる可能性があります。したがって、組み込みクラスを拡張するためのより良い方法は、[_コンポジション_](#継承を避ける) を使用することです。 + +### null を拡張 + +`extends null` は、[`Object.prototype` を継承しないオブジェクト](/ja/docs/Web/JavaScript/Reference/Global_Objects/Object#null-prototype_objects)を簡単に作成できるようにするために設計されました。しかし、コンストラクター内で `super()` を呼び出すべきかどうかが未確定なため、オブジェクトを返さないコンストラクターの実装を使用して、実際にそのようなクラスを構築することは可能ではありません。 [TC39 委員会はこの機能を再び使えるようにするために作業しています](https://github.com/tc39/ecma262/pull/1321)。 + +```js +new (class extends null {})(); +// TypeError: Super constructor null of anonymous class is not a constructor + +new (class extends null { + constructor() {} +})(); +// ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor + +new (class extends null { + constructor() { + super(); + } +})(); +// TypeError: Super constructor null of anonymous class is not a constructor +``` + +代わりに、コンストラクターから明示的にインスタンスを返す必要があります。 + +```js +class NullClass extends null { + constructor() { + // new.target を使用することで、派生クラスが正しいプロトタイプチェーンを + // 持つことができる + return Object.create(new.target.prototype); + } +} + +const proto = Object.getPrototypeOf; +console.log(proto(proto(new NullClass()))); // null +``` ## 例 ### extends の使用 -最初の例では、 `Square` と呼ばれるクラスを `Polygon` と呼ばれるクラスから作成します。この例は、[ライブデモ](https://googlechrome.github.io/samples/classes-es6/index.html) [(ソース)](https://github.com/GoogleChrome/samples/blob/gh-pages/classes-es6/index.html) から転載しています。 +最初の例では、 `Square` と呼ばれるクラスを `Polygon` と呼ばれるクラスから作成します。この例は、[ライブデモ](https://googlechrome.github.io/samples/classes-es6/index.html)[(ソース)](https://github.com/GoogleChrome/samples/blob/gh-pages/classes-es6/index.html)から転載しています。 ```js class Square extends Polygon { @@ -44,34 +195,178 @@ class Square extends Polygon { } ``` -### 組込みオブジェクトでの extends の使用 +### プレーンなオブジェクトの拡張 -この例では、組込みの {{jsxref("Date")}} オブジェクトを拡張します。この例は、[ライブデモ](https://googlechrome.github.io/samples/classes-es6/index.html) [(ソース)](https://github.com/GoogleChrome/samples/blob/gh-pages/classes-es6/index.html) から転載しています。 +クラスは通常の(構築不可能な)オブジェクトを継承することはできません。このオブジェクトのすべてのプロパティを継承したインスタンスで利用できるようにして、通常のオブジェクトを継承したい場合は、代わりに {{jsxref("Object.setPrototypeOf()")}} を使用することができます。 ```js -class myDate extends Date { +const Animal = { + speak() { + console.log(`${this.name}が鳴きます。`); + }, +}; + +class Dog { + constructor(name) { + this.name = name; + } +} + +Object.setPrototypeOf(Dog.prototype, Animal); + +const d = new Dog("ミッチー"); +d.speak(); // ミッチーが鳴きます。 +``` + +### 組み込みオブジェクトでの extends の使用 + +この例では、組み込みの {{jsxref("Date")}} オブジェクトを拡張します。この例は、[ライブデモ](https://googlechrome.github.io/samples/classes-es6/index.html) [(ソース)](https://github.com/GoogleChrome/samples/blob/gh-pages/classes-es6/index.html) から転載しています。 + +```js-nolint +class MyDate extends Date { getFormattedDate() { - var months = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", + const months = [ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; - return ( - this.getDate() + "-" + months[this.getMonth()] + "-" + this.getFullYear() - ); + return `${this.getDate()}-${months[this.getMonth()]}-${this.getFullYear()}`; + } +} +``` + +### `Object` の拡張 + +JavaScript のオブジェクトはすべて既定では `Object.prototype` を継承しているので、一見すると `extends Object` と書くのは冗長に見えます。 `extends` をまったく書かない場合と異なる形で言えば、コンストラクター自体が {{jsxref("Object.keys()")}} のような静的メソッドを `Object` から継承していることくらいです。しかし、どの `Object` の静的メソッドも `this` の値を使用していないため、これらの静的メソッドを継承することに有益な値はありません。 + +{{jsxref("Object/Object", "Object()")}} コンストラクターはサブクラスのシナリオを特殊化します。 [`super()`](/ja/docs/Web/JavaScript/Reference/Operators/super) によって暗黙的に呼び出された場合、常に `new.target.prototype` をプロトタイプとする新しいオブジェクトを初期化します。 `super()` に渡す値は無視されます。 + +```js +class C extends Object { + constructor(v) { + super(v); + } +} + +console.log(new C(1) instanceof Number); // false +console.log(C.keys({ a: 1, b: 2 })); // [ 'a', 'b' ] +``` + +この動作を、サブクラスを特殊化しないカスタムラッパーと比較してみてください。 + +```js +function MyObject(v) { + return new Object(v); +} +class D extends MyObject { + constructor(v) { + super(v); + } +} +console.log(new D(1) instanceof Number); // true +``` + +### species + +派生配列クラス `MyArray` で {{jsxref("Array")}} オブジェクトを返したい場合もあります。 species パターンを使うと、既定のコンストラクターを上書きすることができます。 + +例えば、{{jsxref("Array.prototype.map()")}} のような既定のコンストラクターを返すメソッドを使用する場合、これらのメソッドは `MyArray` オブジェクトの代わりに、親の `Array` オブジェクトを返すようにします。シンボル {{jsxref("Symbol.species")}} を使用すると、これを行うことができます。 + +```js +class MyArray extends Array { + // 親の Array コンストラクターの species を上書き + static get [Symbol.species]() { + return Array; + } +} + +const a = new MyArray(1, 2, 3); +const mapped = a.map((x) => x * x); + +console.log(mapped instanceof MyArray); // false +console.log(mapped instanceof Array); // true +``` + +この動作は、多くの組み込みコピーメソッドで実装されています。この機能の注意点については、[組み込みクラスのサブクラス化](#組み込みクラスのサブクラス化)の説明を参照してください。 + +### ミックスイン + +抽象サブクラスまたはミックスインは、クラスのテンプレートです。クラスはスーパークラスを 1 つしか持つことができないので、例えばツールクラスからの多重継承は不可能です。機能はスーパークラスが提供しなければなりません。 + +スーパークラスを入力とし、そのスーパークラスを拡張したサブクラスを出力とする関数が、ミックスインを実装するために使用することができます。 + +```js +const calculatorMixin = (Base) => + class extends Base { + calc() {} + }; + +const randomizerMixin = (Base) => + class extends Base { + randomize() {} + }; +``` + +これらのミックスインを使用するクラスは、次のように書くことができます。 + +```js +class Foo {} +class Bar extends calculatorMixin(randomizerMixin(Foo)) {} +``` + +### 継承を避ける + +オブジェクト指向プログラミングにおいて、継承はとても強い結合関係です。これは基底クラスのすべての振る舞いが既定でサブクラスに継承されることを意味します。例えば、 `ReadOnlyMap` の実装を考えてみましょう。 + +```js +class ReadOnlyMap extends Map { + set() { + throw new TypeError("A read-only map must be set at construction time."); + } +} +``` + +[`Map()`](/ja/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) コンストラクターはインスタンスの `set()` メソッドを呼び出すからです。 + +```js +const m = new ReadOnlyMap([["a", 1]]); // TypeError: A read-only map must be set at construction time. +``` + +インスタンスが構築済みかどうかを示すためにプライベートなフラグを使用することで、これを回避することができます。しかし、この設計のより重大な問題は、[リスコフの置換原則](https://ja.wikipedia.org/wiki/リスコフの置換原則)を破ってしまうことです。これは、サブクラスはスーパークラスと置換可能であるべきだという状態です。もし関数が `Map` オブジェクトを期待するのであれば、 `ReadOnlyMap` オブジェクトも使用することができるはずです。 + +継承はしばしば[円-楕円問題](https://en.wikipedia.org/wiki/Circle%E2%80%93ellipse_problem)を引き起こします。なぜなら、どちらの型も、多くの共通の特徴を共有しているにもかかわらず、他の型の振る舞いを完全に内包していないからです。一般的に、継承を使用するとてもよい理由がない限り、代わりにコンポジションを使用する方がよいでしょう。コンポジションとは、あるクラスが他のクラスのオブジェクトへの参照を持っていて、そのオブジェクトを実装の詳細としてのみ使用していることを意味しています。 + +```js +class ReadOnlyMap { + #data; + constructor(values) { + this.#data = new Map(values); + } + get(key) { + return this.#data.get(key); + } + has(key) { + return this.#data.has(key); + } + get size() { + return this.#data.size; + } + *keys() { + yield* this.#data.keys(); + } + *values() { + yield* this.#data.values(); + } + *entries() { + yield* this.#data.entries(); + } + *[Symbol.iterator]() { + yield* this.#data[Symbol.iterator](); } } ``` +この場合、 `ReadOnlyMap` クラスは `Map` のサブクラスではありませんが、同じメソッドを実装しています。これはコードの重複を意味しますが、 `ReadOnlyMap` クラスは `Map` クラスと強く割り当てられているわけではなく、 `Map` クラスが変更されても簡単に壊れることはありません。例えば、 `Map` クラスが `set()` を呼び出さない [`emplace()`](https://github.com/tc39/proposal-upsert) メソッドを追加した場合、後者が `emplace()` もオーバーライドするように更新されない限り、 `ReadOnlyMap` クラスは読み取り専用ではなくなります。さらに、`ReadOnlyMap` オブジェクトは `set` メソッドをすべて持たないので、実行時にエラーを発生するよりも正確です。 + ## 仕様書 {{Specifications}} @@ -82,7 +377,8 @@ class myDate extends Date { ## 関連情報 +- [クラスの使用](/ja/docs/Web/JavaScript/Guide/Using_classes)ガイド - [クラス](/ja/docs/Web/JavaScript/Reference/Classes) -- [コンストラクター](/ja/docs/Web/JavaScript/Reference/Classes/constructor) -- [super](/ja/docs/Web/JavaScript/Reference/Operators/super) -- [Anurag Majumdar - Super & Extends in JavaScript](https://medium.com/beginners-guide-to-mobile-web-development/super-and-extends-in-javascript-es6-understanding-the-tough-parts-6120372d3420) +- {{jsxref("Classes/constructor", "constructor")}} +- {{jsxref("Statements/class", "class")}} +- {{jsxref("Operators/super", "super")}} diff --git a/files/ja/web/javascript/reference/classes/index.md b/files/ja/web/javascript/reference/classes/index.md index e8e76cd4efc520..d55bf0ae9dc698 100644 --- a/files/ja/web/javascript/reference/classes/index.md +++ b/files/ja/web/javascript/reference/classes/index.md @@ -1,84 +1,88 @@ --- title: クラス slug: Web/JavaScript/Reference/Classes +l10n: + sourceCommit: 9c4fb236cd9ced12b1eb8e7696d8e6fcb8d8bad3 --- -{{JsSidebar("Classes")}} +{{jsSidebar("Classes")}} -クラスはオブジェクトを作成するためのテンプレートです。それらは、そのデータを処理するためのコードでデータをカプセル化します。JS のクラスはプロトタイプに基づいて構築されていますが、ES5 のクラス風のセマンティクスとは共有されない構文やセマンティクスも持っています。 +クラスはオブジェクトを作成するためのテンプレートです。処理するためのコードでデータをカプセル化します。 JS のクラスは[プロトタイプ](/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)に基づいて構築されていますが、一部の構文や意味はクラスに固有です。 -## クラスの定義 +例や説明については、[クラスの使用](/ja/docs/Web/JavaScript/Guide/Using_classes)ガイドを参照してください。 -クラスは実際には「特別な{{jsxref("Functions", "関数", "", "true")}}」であり、{{jsxref("Operators/function", "関数式", "", "true")}}と{{jsxref("Statements/function", "関数宣言", "", "true")}}を定義することができるように、クラス構文にも{{jsxref("Operators/class", "クラス式", "", "true")}}と{{jsxref("Statements/class", "クラス宣言", "", "true")}}の 2 つの定義方法があります。 +## 解説 -### クラス宣言 +### クラスの定義 -クラスを定義するひとつの方法は、**クラス宣言**を使うことです。クラスを宣言するには、クラス名 (この例では "Rectangle") 付きで `class` キーワードを使います。 +クラスは実際には「特別な[関数](/ja/docs/Web/JavaScript/Reference/Functions)」であり、[関数式](/ja/docs/Web/JavaScript/Reference/Operators/function)と[関数宣言](/ja/docs/Web/JavaScript/Reference/Statements/function)を定義することができるように、クラスも[クラス式](/ja/docs/Web/JavaScript/Reference/Operators/class)または[クラス宣言](/ja/docs/Web/JavaScript/Reference/Statements/class)の 2 つの定義方法があります。 ```js +// 宣言 class Rectangle { constructor(height, width) { this.height = height; this.width = width; } } -``` - -#### 巻き上げ (ホイスティング) - -**関数宣言**と**クラス宣言**の重要な違いは、関数宣言では{{Glossary("Hoisting", "巻き上げ")}}されるのに対し、クラス宣言ではされないことです。クラスにアクセスする前に、そのクラスを宣言する必要があります。そうしないと、{{jsxref("ReferenceError")}} が発生します。 -```js example-bad -const p = new Rectangle(); // ReferenceError - -class Rectangle {} -``` - -### クラス式 - -クラスを定義するもう一つの方法は**クラス式**です。クラス式は、名前付きでも名前なしでもできます。名前付きクラスの名前は、クラス内のローカルとして扱われます。ただし、 (インスタンスのではなく) クラスの {{jsxref("Function.name", "name")}} プロパティによってアクセスできます。 - -```js -// 無名 -let Rectangle = class { +// 式(クラスは無名だが、変数に代入される) +const Rectangle = class { constructor(height, width) { this.height = height; this.width = width; } }; -console.log(Rectangle.name); -// 出力: "Rectangle" -// 名前つき -let Rectangle = class Rectangle2 { +// 式(自分の名前を持つクラス) +const Rectangle = class Rectangle2 { constructor(height, width) { this.height = height; this.width = width; } }; -console.log(Rectangle.name); -// 出力: "Rectangle2" ``` -> **メモ:** クラス**式**にも[クラス宣言](#class_declarations)で言及したのと同様の巻き上げ問題があります。 +関数式と同様に、クラス式も無名であったり、割り当てる変数とは異なる名前を持ったりすることができます。しかし、関数宣言とは異なり、クラス宣言は `let` や `const` と同じ[一時的なデッドゾーン](/ja/docs/Web/JavaScript/Reference/Statements/let#一時的なデッドゾーン)の制約があり、[巻き上げられていない](/ja/docs/Web/JavaScript/Guide/Using_classes#クラス宣言の巻き上げ)かのように動作します。 -## クラス本体とメソッド定義 +### クラス本体 中括弧 `{}` 内にクラス本体を記述します。クラス本体には、メソッドやコンストラクターといったクラスメンバーを記述します。 -### Strict モード +クラス本体は `"use strict"` ディレクティブがなくても[厳格モード](/ja/docs/Web/JavaScript/Reference/Strict_mode)で実行されます。 -クラス本体は {{jsxref("Strict_mode", "Strict モード", "", "true")}}で実行されます。つまり、ここで書かれたコードは、パフォーマンスを向上させるために、より厳密な構文に従います。そうでない場合はサイレントエラーが投げられます。なお、特定のキーワードは将来のバージョンの ECMAScript 用に予約されています。 +クラスの要素は 3 つの側面で特徴付けられます。 -### コンストラクター +- 種類: ゲッター、セッター、メソッド、フィールド +- 場所: 静的またはインスタンス +- 可視性: パブリックまたはプライベート -{{jsxref("Classes/constructor", "コンストラクター", "", "true")}}メソッドは、`class` で作成したオブジェクトを作成して初期化するための特別なメソッドです。"constructor" という名前の特別なメソッドは、クラスに 1 つしか定義できません。クラスに複数のコンストラクターメソッドが存在する場合、{{jsxref("SyntaxError")}} が発生します。 +これらを合計すると、使用可能な組み合わせは16通りになります。リファレンスをより論理的に分割し、コンテンツの重複を避けるため、異なる要素はそれぞれ別のページで詳しく紹介しています。 -スーパークラスのコンストラクターはキーワード `super` を使用して呼び出せます。 +- [メソッド定義](/ja/docs/Web/JavaScript/Reference/Functions/Method_definitions) + - : パブリックインスタンスメソッド +- [ゲッター](/ja/docs/Web/JavaScript/Reference/Functions/get) + - : パブリックインスタンスゲッター +- [セッター](/ja/docs/Web/JavaScript/Reference/Functions/set) + - : パブリックインスタンスセッター +- [パブリッククラスフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields) + - : パブリックインスタンスフィールド +- [`static`](/ja/docs/Web/JavaScript/Reference/Classes/static) + - : パブリック静的メソッド、ゲッター、セッター、フィールド +- [プライベートプロパティ](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties) + - : プライベートなものすべて -### プロトタイプメソッド +> **メモ:** プライベート機能には、同じクラスで宣言するプロパティ名はすべて固有のものでなければならないという制約があります。他のすべてのパブリックプロパティにはこの制限はなく、同じ名前の複数のパブリックプロパティを持つことができ、最後のプロパティが他のプロパティを上書きします。これは[オブジェクト初期化子](/ja/docs/Web/JavaScript/Reference/Operators/Object_initializer#重複したプロパティ名)での処理と同じです。 -[メソッド定義](/ja/docs/Web/JavaScript/Reference/Functions/Method_definitions)を参照してください。 +さらに、特別なクラス要素の構文として、 [`constructor`](#コンストラクター) と[静的初期化ブロック](#静的初期化ブロック)の 2 つがあり、自分自身で参照します。 + +#### コンストラクター + +{{jsxref("Classes/constructor", "constructor")}} メソッドは、クラスで作成したオブジェクトを作成して初期化するための特別なメソッドです。 "constructor" という名前の特別なメソッドは、クラスに 1 つしか定義できません。クラスに複数の `constructor` メソッドが存在した場合、{{jsxref("SyntaxError")}} が発生します。 + +スーパークラスのコンストラクターはキーワード [`super`](/ja/docs/Web/JavaScript/Reference/Operators/super) を使用して呼び出すことができます。 + +コンストラクターの中でインスタンスプロパティを作成することができます。 ```js class Rectangle { @@ -86,46 +90,52 @@ class Rectangle { this.height = height; this.width = width; } - // ゲッター - get area() { - return this.calcArea(); - } - // メソッド - calcArea() { - return this.height * this.width; - } } +``` -const square = new Rectangle(10, 10); +また、インスタンスプロパティの値がコンストラクターの引数に依存しない場合は、[クラスフィールド](#フィールド宣言)として定義することもできます。 -console.log(square.area); // 100 -``` +#### 静的初期化ブロック -### ジェネレーターメソッド +[静的初期化ブロック](/ja/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks)は、初期化中に文の評価を含めることができ、プライベートスコープにアクセスしながら、静的プロパティの柔軟な初期化を行うことができます。 -[イテレーターとジェネレーター](/ja/docs/Web/JavaScript/Guide/Iterators_and_Generators)も参照してください。 +静的ブロックは複数宣言することができ、静的フィールドや静的メソッドの宣言と混在させることができます(静的項目はすべて宣言順に評価されます)。 + +#### メソッド + +メソッドはクラスごとにプロトタイプに定義され、すべてのインスタンスで共有されます。メソッドには、プレーン関数、非同期関数、ジェネレーター関数、非同期ジェネレーター関数があります。詳しい情報は、[メソッド定義](/ja/docs/Web/JavaScript/Reference/Functions/Method_definitions)を参照してください。 ```js -class Polygon { - constructor(...sides) { - this.sides = sides; +class Rectangle { + constructor(height, width) { + this.height = height; + this.width = width; + } + // Getter + get area() { + return this.calcArea(); } // Method + calcArea() { + return this.height * this.width; + } *getSides() { - for (const side of this.sides) { - yield side; - } + yield this.height; + yield this.width; + yield this.height; + yield this.width; } } -const pentagon = new Polygon(1, 2, 3, 4, 5); +const square = new Rectangle(10, 10); -console.log([...pentagon.getSides()]); // [1,2,3,4,5] +console.log(square.area); // 100 +console.log([...square.getSides()]); // [10, 10, 10, 10] ``` -### 静的メソッドとプロパティ +#### 静的メソッドとフィールド -{{jsxref("Classes/static", "static", "", "true")}} キーワードは、クラスの静的メソッドまたはプロパティを定義します。静的メンバー (プロパティとメソッド) は、クラスを[インスタンス化](/ja/docs/Learn/JavaScript/Objects/Object-oriented_JS#constructors_and_object_instances)せずに呼び出され、クラスインスタンスを介して呼び出すことは**できません**。静的メソッドは、アプリケーションのユーティリティ関数を作成するためによく使用されますが、静的プロパティは、キャッシュ、固定構成、またはインスタンス間で複製する必要のないその他のデータに役立ちます。 +{{jsxref("Classes/static", "static")}} キーワードは、クラスの静的メソッドまたはフィールドを定義します。静的プロパティ(フィールドとメソッド)は、インスタンスごとに定義されるのではなく、クラス自体に定義されます。静的メソッドは、アプリケーションのユーティリティ関数を作成するために使用することが多く、一方、静的フィールドは、キャッシュや修正された構成など、インスタンス間で複製する必要のないデータに有益です。 ```js class Point { @@ -154,69 +164,9 @@ console.log(Point.displayName); // "Point" console.log(Point.distance(p1, p2)); // 7.0710678118654755 ``` -### プロトタイプと静的メソッドに対する `this` の結び付け - -`this` に値が付けられずに静的メソッドまたはプロトタイプメソッドが呼ばれると、{{jsxref("Operators/this", "this")}} の値はメソッド内で `undefined` になります。たとえ {{jsxref("Strict_mode", "\"use strict\"")}} ディレクティブがなくても同じふるまいになります。なぜなら、`class` 本体の中のコードは常に Strict モードで実行されるからです。 - -```js -class Animal { - speak() { - return this; - } - static eat() { - return this; - } -} - -let obj = new Animal(); -obj.speak(); // Animal オブジェクト -let speak = obj.speak; -speak(); // undefined - -Animal.eat(); // class Animal -let eat = Animal.eat; -eat(); // undefined -``` - -上のコードを従来の関数ベースの構文を使って書くと、非 Strict モードでは、最初の `this` の値をもとにして、メソッド呼び出しの中で自動的に結び付けられます。最初の値が `undefined` の場合、`this` にはグローバルオブジェクトが入ります。 Strict モードでは自動結び付けは行われません。`this` の値はそのまま渡されます。 - -```js -function Animal() {} - -Animal.prototype.speak = function () { - return this; -}; - -Animal.eat = function () { - return this; -}; - -let obj = new Animal(); -let speak = obj.speak; -speak(); // グローバルオブジェクト (Strict モードではない) - -let eat = Animal.eat; -eat(); // グローバルオブジェクト (Strict モードではない) -``` - -### インスタンスプロパティ - -インスタンスプロパティはクラスのメソッドの中で定義しなければなりません。 - -```js -class Rectangle { - constructor(height, width) { - this.height = height; - this.width = width; - } -} -``` - -### フィールド宣言 +#### フィールド宣言 -#### パブリックフィールド宣言 - -JavaScript のフィールド宣言構文を使って、上記の例は次のように書くことができます。 +クラスのフィールド宣言構文では、[コンストラクター](#コンストラクター)の例は次のように書くことができます。 ```js class Rectangle { @@ -229,15 +179,15 @@ class Rectangle { } ``` -フィールドを事前宣言することで、クラス定義はより自己文書化され、フィールドは常に存在するようになります。 +クラスのフィールドはオブジェクトのプロパティに似ていて変数ではないので、宣言するために `const` などのキーワードは使用しません。 JavaScript では、[プライベート機能](#プライベートクラス機能)は特別な識別子構文を使うので、 `public` や `private` のような修飾子キーワードも使うべきではありません。 -上記のように、フィールドはデフォルト値の有無にかかわらず宣言できます。 +上で見たように、フィールドは既定値付きで宣言することも、既定値なしで宣言することもできます。既定値のないフィールドは `undefined` が既定値となります。フィールドを前もって宣言することで、クラスの宣言がよりドキュメント化され、フィールドが常に存在するようになり、最適化しやすくします。 -詳しい情報は、{{jsxref("Classes/Public_class_fields", "パブリッククラスフィールド", "", "true")}}を参照してください。 +詳細は[パブリッククラスフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields)を参照してください。 -#### プライベートフィールド宣言 +#### プライベートプロパティ -プライベートフィールドを使うと、宣言は下記のように洗練できます。 +プライベートフィールドを使うと、宣言は下記のように宣言できます。 ```js class Rectangle { @@ -250,17 +200,16 @@ class Rectangle { } ``` -プライベートフィールドの参照はクラス本体内でのみ可能となり、クラス外からの参照はエラーとなります。クラス外からは見えないものを定義することで、クラスのユーザーが (変更される可能性のある) 内部状態に依存できないようにします。 - -> **メモ:** プライベートフィールドは、事前宣言のみ可能です。 +クラスの外部からプライベートフィールドを参照するとエラーになります。これらのフィールドはクラス本体の内部でのみ読み書きできます。 +クラスの外から見えないことを定義することで、クラスのユーザーがバージョンごとに変わる内部構造に依存しないこと保証することができます。 -プライベートフィールドは通常のプロパティとは違い、`this` への追加によって後から作成することができません。 +プライベートフィールドは、フィールド宣言でのみ宣言できます。通常のプロパティのように、後から割り当てることで作成することはできません。 -詳しい情報は、{{jsxref("Classes/Private_class_fields", "プライベートクラスフィールド", "", "true")}}を参照してください。 +詳細情報については、[プライベートプロパティ](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties)を参照してください。 -## `extends` によるサブクラス化 +### 継承 -{{jsxref("Classes/extends", "extends")}} キーワードは、クラスを別クラスの子として作成するために、*クラス宣言*または*クラス式*の中で使います。 +{{jsxref("Classes/extends", "extends")}} キーワードは、クラスを別なクラス(クラスまたは関数)の子として作成するために、*クラス宣言*または*クラス式*の中で使います。 ```js class Animal { @@ -269,166 +218,126 @@ class Animal { } speak() { - console.log(`${this.name} makes a noise.`); + console.log(`${this.name}が鳴きます。`); } } class Dog extends Animal { constructor(name) { - super(name); // スーパークラスのコンストラクターを呼び出し、name パラメータを渡す + super(name); // スーパークラスのコンストラクターを呼び出し、 name 引数を渡す } speak() { - console.log(`${this.name} barks.`); + console.log(`${this.name}が吠えます。`); } } -let d = new Dog("Mitzie"); -d.speak(); // Mitzie barks. +const d = new Dog("ミッチー"); +d.speak(); // ミッチーが吠えます。 ``` -サブクラスにコンストラクターが存在する場合は、"this" を使う前に super() を呼ぶ必要があります。 - -従来の関数ベースの「クラス」も拡張できます。 +サブクラスにコンストラクターが存在する場合は、 `this` を使用する前に、最初に `super()` を呼び出す必要があります。キーワード {{jsxref("Operators/super", "super")}} を使用して、スーパークラスの対応するメソッドを呼び出すこともできます。 ```js -function Animal(name) { - this.name = name; -} - -Animal.prototype.speak = function () { - console.log(`${this.name} makes a noise.`); -}; +class Cat { + constructor(name) { + this.name = name; + } -class Dog extends Animal { speak() { - console.log(`${this.name} barks.`); + console.log(`${this.name}が鳴きます。`); } } -let d = new Dog("Mitzie"); -d.speak(); // Mitzie barks. - -// 同様のメソッドは、子のメソッドが親のメソッドよりも優先されます。 -``` - -クラスは通常の (生成不可能な) オブジェクトを拡張できないことに注意してください。通常のオブジェクトから継承したければ、代わりに {{jsxref("Object.setPrototypeOf()")}} を使います。 - -```js -const Animal = { +class Lion extends Cat { speak() { - console.log(`${this.name} makes a noise.`); - }, -}; - -class Dog { - constructor(name) { - this.name = name; + super.speak(); + console.log(`${this.name}が吠えます。`); } } -// このコードが無いと、speak() を実行した時に TypeError になります。 -Object.setPrototypeOf(Dog.prototype, Animal); - -let d = new Dog("Mitzie"); -d.speak(); // Mitzie makes a noise. +const l = new Lion("ファジー"); +l.speak(); +// ファジーが鳴きます。 +// ファジーが吠えます。 ``` -## Species +### 評価の順序 -Array の派生クラスである `MyArray` の中で {{jsxref("Array")}} オブジェクトを返したいときもあるでしょう。species パターンは、デフォルトコンストラクターを上書きすることができます。 +[`class` 宣言](/ja/docs/Web/JavaScript/Reference/Statements/class)や [`class` 式](/ja/docs/Web/JavaScript/Reference/Operators/class)が評価されるとき、そのさまざまな構成要素は以下の順序で評価されます。 -例えば、デフォルトコンストラクターを返す {{jsxref("Array.map", "map()")}} のようなメソッドを使っているとき、`MyArray` ではなく `Array` オブジェクトを返したいでしょう。{{jsxref("Symbol.species")}} シンボルを使うと次のように実現できます。 - -```js -class MyArray extends Array { - // species を親の Array コンストラクターで上書きする - static get [Symbol.species]() { - return Array; - } -} +1. 最初に {{jsxref("Classes/extends", "extends")}} 節があれば、それが評価されます。これは有効なコンストラクター関数か `null` でなければならず、そうでなければ {{jsxref("TypeError")}} が発生します。 +2. {{jsxref("Classes/constructor", "constructor")}} メソッドが抽出され、 `constructor` が存在しない場合は既定の実装で置き換えられます。しかし、 `constructor` の定義は単なるメソッド定義であるため、この段階は観察することができません。 +3. クラス要素のプロパティキーは宣言順に評価されます。プロパティキーが計算されたものである場合、計算された式が評価され、 `this` 値にはクラスを囲む `this` 値が設定されます(クラスそのものではありません)。プロパティ値はまだ評価されません。 +4. メソッドとアクセサーは宣言順にインストールされます。インスタンスメソッドとアクセサーは現在のクラスの `prototype` プロパティにインストールされ、静的メソッドとアクセサーはクラス自体にインストールされます。プライベートインスタンスメソッドとアクセサーは、後でインスタンスに直接インストールするために保存されます。この段階は観察することができません。 +5. これでクラスは `extends` で指定したプロトタイプと `constructor` で指定した実装で初期化されます。上記のすべての段階で、評価された式がクラスの名前にアクセスしようとすると、クラスがまだ初期化されていないため {{jsxref("ReferenceError")}} が発生します。 +6. クラス要素の値は宣言順に評価されます。 + - [インスタンスフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields)(パブリックまたはプライベート)ごとに、その初期化子式が保存されます。初期化子はインスタンス生成時、コンストラクタの開始時(基底クラスの場合)、または `super()` 呼び出しが返す直前(派生クラスの場合)に評価されます。 + - それぞれの[静的フィールド](/ja/docs/Web/JavaScript/Reference/Classes/static)(パブリックまたはプライベート)について、その初期化子は `this` をクラス自身に設定した状態で評価され、プロパティがクラス上に作成されます。 + - [静的初期化ブロック](/ja/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks)は `this` をクラス自身に設定した状態で評価されます。 +7. これでクラスは完全に初期化され、コンストラクター関数として使用することができます。 -let a = new MyArray(1, 2, 3); -let mapped = a.map((x) => x * x); +インスタンスがどのように作成されるかは、 {{jsxref("Classes/constructor", "constructor")}} を参照してください。 -console.log(mapped instanceof MyArray); // false -console.log(mapped instanceof Array); // true -``` +## 例 -## `super` でスーパークラスを呼び出す +### インスタンスメソッドと静的メソッドのバインド -{{jsxref("Operators/super", "super")}} キーワードを使ってスーパークラスのメソッドを呼び出せます。これはプロトタイプベースの継承よりも優れています。 +静的メソッドやインスタンスメソッドを {{jsxref("Operators/this", "this")}} の値なしで呼び出した場合、例えばメソッドを変数に割り当ててから呼び出すと、メソッド内部では `this` の値が `undefined` になります。この動作は、 [`"use strict"`](/ja/docs/Web/JavaScript/Reference/Strict_mode) ディレクティブが存在しない場合でも同じです。 `class` 本体の中のコードは常に厳格モードで実行されるからです。 ```js -class Cat { - constructor(name) { - this.name = name; - } - +class Animal { speak() { - console.log(`${this.name} makes a noise.`); + return this; } -} - -class Lion extends Cat { - speak() { - super.speak(); - console.log(`${this.name} roars.`); + static eat() { + return this; } } -let l = new Lion("Fuzzy"); -l.speak(); -// Fuzzy makes a noise. -// Fuzzy roars. -``` - -## ミックスイン - -抽象的なサブクラスや*ミックスイン*はクラスのためのテンプレートです。ECMAScript のクラスは 1 つだけスーパークラスを持つことができます。そのため、多重継承はできません。機能はスーパークラスから提供されます。 - -ECMAScript では、スーパークラスをインプットとして、そしてスーパークラスを継承した派生クラスをアウトプットとする関数をミックスインで実装できます。 +const obj = new Animal(); +obj.speak(); // the Animal object +const speak = obj.speak; +speak(); // undefined -```js -let calculatorMixin = (Base) => - class extends Base { - calc() {} - }; - -let randomizerMixin = (Base) => - class extends Base { - randomize() {} - }; +Animal.eat(); // class Animal +const eat = Animal.eat; +eat(); // undefined ``` -ミックスインを使用したクラスを次のように記述することもできます。 +厳格モードでない従来の関数ベースの構文で上記を書き直すと、 `this` メソッド呼び出しは自動的に {{jsxref("globalThis")}} にバインドされます。厳格モードでは `this` の値は `undefined` のままです。 ```js -class Foo {} -class Bar extends calculatorMixin(randomizerMixin(Foo)) {} -``` +function Animal() {} -## クラス定義の再実行 +Animal.prototype.speak = function () { + return this; +}; -クラスを再定義することはできません。再定義しようとすると `SyntaxError` が発生します。 +Animal.eat = function () { + return this; +}; -Firefox のウェブコンソール(**メニュー** > **ウェブ開発** > **ウェブコンソール**)などでコードを試しているときに、同じ名前のクラス定義を 2 回実行すると、`SyntaxError: redeclaration of let ClassName` が発生します。(この問題については [Firefox バグ 1428672](https://bugzil.la/1428672) でさらに詳しく説明しています。)Chrome 開発者ツールで同様の操作を行うと、`Uncaught SyntaxError: Identifier 'ClassName' has already been declared at :1:1` のようなメッセージが表示されます。 +const obj = new Animal(); +const speak = obj.speak; +speak(); // global object (in non–strict mode) + +const eat = Animal.eat; +eat(); // global object (in non-strict mode) +``` -## Specifications +## 仕様書 {{Specifications}} -## Browser compatibility +## ブラウザーの互換性 {{Compat}} -## See also +## 関連情報 -- {{jsxref("Functions", "関数", "", "true")}} -- {{jsxref("Statements/class", "クラス宣言", "", "true")}} -- {{jsxref("Operators/class", "クラス式", "", "true")}} -- {{jsxref("Classes/Public_class_fields", "パブリッククラスフィールド", "", "true")}} -- {{jsxref("Classes/Private_class_fields", "プライベートクラスフィールド", "", "true")}} -- {{jsxref("Operators/super", "super")}} -- [ブログ記事: "ES6 In Depth: Classes"](https://hacks.mozilla.org/2015/07/es6-in-depth-classes/) -- [Fields and public/private class properties proposal (stage 3)](https://github.com/tc39/proposal-class-fields) +- [クラスの使用](/ja/docs/Web/JavaScript/Guide/Using_classes)ガイド +- [`class`](/ja/docs/Web/JavaScript/Reference/Statements/class) +- [`class` 式](/ja/docs/Web/JavaScript/Reference/Operators/class) +- [関数](/ja/docs/Web/JavaScript/Reference/Functions) +- [ES6 In Depth: Classes](https://hacks.mozilla.org/2015/07/es6-in-depth-classes/) (hacks.mozilla.org, 2015) diff --git a/files/ja/web/javascript/reference/classes/private_properties/index.md b/files/ja/web/javascript/reference/classes/private_properties/index.md index 3c67d5fae9d9d7..9334debf3922f7 100644 --- a/files/ja/web/javascript/reference/classes/private_properties/index.md +++ b/files/ja/web/javascript/reference/classes/private_properties/index.md @@ -1,63 +1,133 @@ --- -title: プライベートクラス機能 +title: プライベートプロパティ slug: Web/JavaScript/Reference/Classes/Private_properties -original_slug: Web/JavaScript/Reference/Classes/Private_class_fields +l10n: + sourceCommit: 3ae834dd1eaba420c78d36c903bf178cdd5fbb7a --- -{{JsSidebar("Classes")}} +{{jsSidebar("Classes")}} -クラスのフィールドは既定で{{ jsxref('Classes/Public_class_fields','公開', '', 1) }}ですが、ハッシュ `#` 接頭辞を使ってプライベートクラスメンバーを生成することができます。これらのクラス機能のプライバシーのカプセル化は、JavaScript 自身によって行われます。 +**プライベートプロパティ**は、パブリックである通常のクラスプロパティ、例えば[クラスフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields)やクラスメソッドなどに対するものです。プライベートプロパティはハッシュ `#` 接頭辞を使用して作成され、クラスの外部から合法的に参照することはできません。これらのクラスプロパティのプライバシーカプセル化は JavaScript 自身によって強制されます。 + +プライベートのプロパティは、この構文が存在する以前はネイティブではありませんでした。プロトタイプ継承では、 [`WeakMap`](/ja/docs/Web/JavaScript/Reference/Global_Objects/WeakMap#emulating_private_members) オブジェクトや[クロージャ](/ja/docs/Web/JavaScript/Closures#emulating_private_methods_with_closures)でその振る舞いをエミュレートすることができますが、使いやすさの面からは `#` 構文にはかないません。 ## 構文 -```js -class ClassWithPrivateField { +```js-nolint +class ClassWithPrivate { #privateField; -} + #privateFieldWithInitializer = 42; -class ClassWithPrivateMethod { #privateMethod() { - return "hello world"; + // … } -} -class ClassWithPrivateStaticField { - static #PRIVATE_STATIC_FIELD; -} + static #privateStaticField; + static #privateStaticFieldWithInitializer = 42; -class ClassWithPrivateStaticMethod { static #privateStaticMethod() { - return "hello world"; + // … } } ``` -## 例 +さらにいくつかの構文上の制約があります。 -### プライベートインスタンスフィールド +- クラス内で宣言するプライベート識別子はすべて固有のものである必要があります。名前空間は、静的プロパティとインスタンスプロパティの間で共有されます。唯一の例外は、 2 つの宣言がゲッターとセッターのペアを定義している場合です。 +- プライベート識別子は `#constructor` にすることができません。 -プライベートインスタンスフィールドは **# 名** (*ハッシュ名*と読む) で宣言され、これは `#` の接頭辞が付いた識別子です。この `#` は名前の一部で、宣言やアクセスにも使われます。プライベートフィールドはクラスのコンストラクター上でクラス定義自身の中から参照できます。フィールド名の宣言や、フィールドの値へのアクセスに使用されます。 +## 解説 -スコープ外から `#` 名を参照すると構文エラーになります。また、呼び出される前に宣言されていないプライベートフィールドを参照したり、宣言されているフィールドを `delete` で削除しようとしても構文エラーになります。 +ほとんどのクラスプロパティには、プライベートの対応するものがあります。 -```js example-bad +- プライベートフィールド +- プライベートメソッド +- プライベート静的フィールド +- プライベート静的メソッド +- プライベートゲッター +- プライベートセッター +- プライベート静的ゲッター +- プライベート静的セッター + +これらの機能をまとめてプライベートプロパティと呼びます。しかし、JavaScript で[コンストラクター](/ja/docs/Web/JavaScript/Reference/Classes/constructor)をプライベートにすることはできません。クラスの外部でクラスが構築されないようにするには、[プライベートフラグを使用する](#プライベートコンストラクターをシミュレーション)必要があります。 + +プライベートプロパティは **# 名前**(「ハッシュ-名前」と読みます)で宣言され、接頭辞として `#` が付けられます。ハッシュ接頭辞はプロパティ名の一部です。古いアンダースコア接頭辞規約 `_privateField` との関係を思い描いてください。しかし、普通の文字列プロパティではないので、[ブラケット記法](/ja/docs/Web/JavaScript/Reference/Operators/Property_accessors#ブラケット記法)を使って動的にアクセスすることはできません。 + +スコープ外から `#` 付きの名を参照すると構文エラーになります。また、呼び出される前に宣言されていないプライベートフィールドを参照したり、宣言されているフィールドを [`delete`](/ja/docs/Web/JavaScript/Reference/Operators/delete) で削除しようとしても構文エラーになります。 + +```js-nolint example-bad class ClassWithPrivateField { #privateField; - constructor() { - this.#privateField = 42; - delete this.#privateField; // Syntax error - this.#undeclaredField = 444; // Syntax error + constructor() {; + delete this.#privateField; // Syntax error + this.#undeclaredField = 42; // Syntax error } } -const instance = new ClassWithPrivateField() -instance.#privateField === 42; // Syntax error +const instance = new ClassWithPrivateField(); +instance.#privateField; // Syntax error ``` -> **メモ:** [`in`](/ja/docs/Web/JavaScript/Reference/Operators/in) 演算子を使用すると、プライベートフィールド (またはプライベートメソッド) が欠けているかどうかをチェックできます。そのプライベートフィールドが存在すれば `true` を返し、そうでなければ `false` を返します。 +JavaScript は動的言語ですが、ハッシュ識別子の構文が特殊であり、構文レベルで通常のプロパティとは異なるため、このコンパイル時チェックを行うことができます。 + +> **メモ:** Chrome コンソールで実行するコードは、クラス外からプライベートプロパティにアクセスすることができます。これは JavaScript の構文制限を開発者ツール限定で緩和したものです。 + +プロパティを持っていないオブジェクトからプライベートプロパティにアクセスすると、通常のプロパティのように `undefined` を返すのではなく、{{jsxref("TypeError")}} が発生します。 + +```js example-bad +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 +``` -パブリックフィールドのように、プライベートフィールドは基底クラスの構築辞典、またはサブクラス内で `super()` が呼び出される場面で追加されます。 +この例では、静的関数の中や、外部で定義したクラスのインスタンスでもプライベートプロパティにアクセスできることも示しています。 + +[`in`](/ja/docs/Web/JavaScript/Reference/Operators/in) 演算子を使用すると、プライベートフィールド(またはプライベートメソッド)が存在するかどうかをチェックできます。そのプライベートフィールドが存在すれば `true` を返し、そうでなければ `false` を返します。 + +```js example-good +class C { + #x; + constructor(x) { + this.#x = x; + } + static getX(obj) { + if (#x in obj) return obj.#x; + + return "obj must be an instance of 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()))); // the current date and time +console.log(C.getX({})); // "obj must be an instance of C" +``` + +オブジェクトが現在のクラスのプライベートプロパティを 1 つでも持っていることが見つかった場合(`try...catch`または`in`チェックのいずれか)、他にもプライベートプロパティをすべて持っている必要があります。あるクラスのプライベートプロパティを持つオブジェクトは、一般的にそのクラスによって構築されたことを意味しています([常にではありません](#オーバーライドしたオブジェクトの返却))。 + +プライベートプロパティは、現在のクラス本体内でのみアクセスすることができ、サブクラスには継承されないため、[プロトタイプ継承モデル](/ja/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)にはありません。クラスが異なると、プライベートプロパティは同じ名前でも全く異なるものであり、相互運用はできません。クラスごとに管理される、インスタンスに付加された外部メタデータとして考えてください。このため、 {{jsxref("Object.freeze()")}} や {{jsxref("Object.seal()")}} は、プライベートプロパティには影響しません。 + +プライベートフィールドがいつ、どのように初期化されるかについての詳細は、[プライベートクラスフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields)を参照してください。 + +## 例 + +### プライベートフィールド + +プライベートフィールドには、プライベートインスタンスフィールドとプライベート静的フィールドがあります。プライベートフィールドは、クラス宣言の内部からのみアクセスすることができます。 + +#### プライベートインスタンスフィールド + +プライベートインスタンスフィールドは、パブリック版と次の点で似ています。 + +- 基底クラスでコンストラクターが実行される前か、サブクラスで [`super()`](/ja/docs/Web/JavaScript/Reference/Operators/super) を呼び出した直後に追加されます。 +- そのクラスのインスタンスでのみ利用できます。 ```js class ClassWithPrivateField { @@ -68,7 +138,7 @@ class ClassWithPrivateField { } } -class SubClass extends ClassWithPrivateField { +class Subclass extends ClassWithPrivateField { #subPrivateField; constructor() { @@ -77,82 +147,130 @@ class SubClass extends ClassWithPrivateField { } } -new SubClass(); -// SubClass {#privateField: 42, #subPrivateField: 23} +new Subclass(); // 開発者ツールでは Subclass {#privateField: 42, #subPrivateField: 23} と表示 +``` + +> **メモ:** 基底クラス `ClassWithPrivateField` の `#privateField` は `ClassWithPrivateField` のプライベートメンバーであり、派生クラス `Subclass` からはアクセスできません。 + +#### オーバーライドしたオブジェクトの返却 + +クラスのコンストラクターは異なるオブジェクトを返すことができ、そのオブジェクトは派生クラスのコンストラクターの新しい `this` として使用することができます。派生クラスは、返されたオブジェクトにおいてプライベートフィールドを定義することができます。つまり、無関係なオブジェクトにプライベートフィールドを「刻印」することが可能です。 + +```js +class Stamper extends class { + // コンストラクターが指定されたオブジェクトを返す基底クラス + constructor(obj) { + return obj; + } +} { + // この宣言は、基底クラスのコンストラクターが返すオブジェクトに + // プライベートフィールドを「刻印」する + #stamp = 42; + static getStamp(obj) { + return obj.#stamp; + } +} + +const obj = {}; +new Stamper(obj); +// `Stamper` は `Base` を呼び出して `obj` を返す。次に +// `Stamper` は `obj` に `#stamp` を定義する + +console.log(obj); // 一部の開発者ツールでは {#stamp: 42} と表示 +console.log(Stamper.getStamp(obj)); // 42 +console.log(obj instanceof Stamper); // false + +// プライベートプロパティに 2 度刻印することはできません。 +new Stamper(obj); // Error: Initializing an object twice is an error with private fields ``` +> **警告:** これはとても混乱を招きかねないことです。一般的に、コンストラクターから任意のものを返すのは避けるようにしましょう。特に `this` に関係のないものを返すのは避けるようにしましょう。 + ### プライベート静的フィールド -プライベートフィールドは、クラスのコンストラクター上でクラス宣言の内部からアクセスできます。 -静的変数は静的メソッドからのみ呼び出せるという制限はまだあります。 +プライベート静的フィールドは、パブリック版と次の点で似ています。 + +- クラスが評価される際にクラスのコンストラクターに追加されます。 +- クラス自身からのみ利用できます。 ```js class ClassWithPrivateStaticField { - static #PRIVATE_STATIC_FIELD; + static #privateStaticField = 42; static publicStaticMethod() { - ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD = 42; - return ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD; + return ClassWithPrivateStaticField.#privateStaticField; } } -console.log(ClassWithPrivateStaticField.publicStaticMethod() === 42); -// true +console.log(ClassWithPrivateStaticField.publicStaticMethod()); // 42 ``` -プライベート静的フィールドには制限があります。プライベート静的フィールドを定義したクラスのみが、そのフィールドにアクセスできます。**`this`** を使用すると、予期しない動作をする可能性があります。 -次の例では、`SubClass.basePublicStaticMethod()` を呼び出そうとしたときに、これが (`BaseClassWithPrivateStaticField` クラスではなく) `SubClass` クラスを参照してしまい、 `TypeError` が発生します。 +プライベート静的フィールドには制限があります。プライベート静的フィールドを定義したクラスのみが、そのフィールドにアクセスできます。 [`this`](/ja/docs/Web/JavaScript/Reference/Operators/this) を使用すると、予期しない動作をする可能性があります。次の例では、 `SubClass.basePublicStaticMethod()` を呼び出そうとしたときに `this` が `Subclass` クラスを(`BaseClassWithPrivateStaticField` クラスではなく)参照してしまい、 `TypeError` が発生します。 ```js -class BaseClassWithPrivateStaticField { - static #PRIVATE_STATIC_FIELD; +class ClassWithPrivateStaticField { + static #privateStaticField = 42; - static basePublicStaticMethod() { - this.#PRIVATE_STATIC_FIELD = 42; - return this.#PRIVATE_STATIC_FIELD; + static publicStaticMethod() { + return this.#privateStaticField; } } -class SubClass extends BaseClassWithPrivateStaticField {} +class Subclass extends ClassWithPrivateStaticField {} + +Subclass.publicStaticMethod(); // TypeError: Cannot read private member #privateStaticField from an object whose class did not declare it +``` + +これは、そのメソッドに `super` を付けて呼び出すのと同じです。 [`super` のメソッドはスーパークラスを `this` として呼び出すわけではない](/ja/docs/Web/JavaScript/Reference/Operators/super#静的メソッドでの_super_の呼び出し)からです。 + +```js +class ClassWithPrivateStaticField { + static #privateStaticField = 42; -let error = null; + static publicStaticMethod() { + // super を呼び出しても、 `this` は Subclass を参照している + return this.#privateStaticField; + } +} -try { - SubClass.basePublicStaticMethod(); -} catch (e) { - error = e; +class Subclass extends ClassWithPrivateStaticField { + static callSuperMethod() { + return super.publicStaticMethod(); + } } -console.log(error instanceof TypeError); -// true -console.log(error); -// TypeError: Cannot write private member #PRIVATE_STATIC_FIELD -// to an object whose class did not declare it +Subclass.callSuperMethod(); // TypeError: Cannot read private member #privateStaticField from an object whose class did not declare it ``` +静的プライベートフィールドには常に `this` ではなく、クラス名でアクセスするようにしてください。 + ### プライベートメソッド +プライベートメソッドには、プライベートインスタンスメソッドとプライベート静的メソッドがあります。プライベートメソッドは、クラス宣言の内部からのみアクセスすることができます。 + #### プライベートインスタンスメソッド -プライベートインスタンスメソッドは、プライベートインスタンスフィールドと同様にアクセスが制限されているクラスインスタンスで利用できるメソッドです。 +プライベートインスタンスメソッドは、パブリック版と次の点で異なります。 + +- インスタンスフィールドが導入される前に、直ちに導入されます。 +- クラスのインスタンスでのみ利用可能であり、 `.prototype` プロパティでは利用できません。 ```js class ClassWithPrivateMethod { #privateMethod() { - return "hello world"; + return 42; } - getPrivateMessage() { + publicMethod() { return this.#privateMethod(); } } const instance = new ClassWithPrivateMethod(); -console.log(instance.getPrivateMessage()); -// hello world +console.log(instance.publicMethod()); // 42 ``` -プライベートインスタンスメソッドは、ジェネレーター関数、非同期関数、非同期ジェネレーター関数のいずれかになります。プライベートなゲッターやセッターも可能です。 +プライベートインスタンスメソッドは、ジェネレーター、非同期、非同期ジェネレーターの関数にすることができます。プライベートゲッターとセッターも可能で、パブリックの[ゲッター](/ja/docs/Web/JavaScript/Reference/Functions/get)と[セッター](/ja/docs/Web/JavaScript/Reference/Functions/set)と同じ構文に従います。 ```js class ClassWithPrivateAccessor { @@ -171,13 +289,30 @@ class ClassWithPrivateAccessor { } } -new ClassWithPrivateAccessor(); -// 🎬hello world🛑 +new ClassWithPrivateAccessor(); // 🎬hello world🛑 +``` + +パブリックメソッドと異なり、プライベートメソッドはクラスの `.prototype` プロパティからアクセスすることはできません。 + +```js +class C { + #method() {} + + static getMethod(x) { + return x.#method; + } +} + +console.log(C.getMethod(new C())); // [Function: #method] +console.log(C.getMethod(C.prototype)); // TypeError: Receiver must be an instance of class C ``` #### プライベート静的メソッド -プライベート静的メソッドは、パブリックと同様に、クラスのインスタンスではなく、クラス自体から呼び出されます。プライベート静的フィールドと同様に、クラス宣言の内部からのみアクセス可能です。 +プライベート静的メソッドは、パブリック版と次の点で似ています。 + +- クラスの評価時点でクラスのコンストラクターに追加されます。 +- クラス自身からのみ利用できます。 ```js class ClassWithPrivateStaticMethod { @@ -185,19 +320,12 @@ class ClassWithPrivateStaticMethod { return 42; } - static publicStaticMethod1() { + static publicStaticMethod() { return ClassWithPrivateStaticMethod.#privateStaticMethod(); } - - static publicStaticMethod2() { - return this.#privateStaticMethod(); - } } -console.log(ClassWithPrivateStaticMethod.publicStaticMethod1() === 42); -// true -console.log(ClassWithPrivateStaticMethod.publicStaticMethod2() === 42); -// true +console.log(ClassWithPrivateStaticMethod.publicStaticMethod()); // 42 ``` プライベート静的メソッドは、ジェネレーター関数、非同期関数、非同期ジェネレーター関数、などがあります。 @@ -205,30 +333,51 @@ console.log(ClassWithPrivateStaticMethod.publicStaticMethod2() === 42); 前述のプライベート静的フィールドの制限は、プライベート静的メソッドにも当てはまり、**`this`** を同様に使用すると予期しない動作が発生する可能性があります。次の例では、 `Derived.publicStaticMethod2()` を呼び出そうとすると、これは `Base` クラスではなく `Derived` クラスを参照するため、`TypeError` が発生します。 ```js -class Base { +class ClassWithPrivateStaticMethod { static #privateStaticMethod() { return 42; } - static publicStaticMethod1() { - return Base.#privateStaticMethod(); - } - static publicStaticMethod2() { + + static publicStaticMethod() { return this.#privateStaticMethod(); } } -class Derived extends Base {} +class Subclass extends ClassWithPrivateStaticMethod {} + +console.log(Subclass.publicStaticMethod()); // TypeError: Cannot read private member #privateStaticMethod from an object whose class did not declare it +``` + +### プライベートコンストラクターをシミュレーション + +他の多くの言語では、コンストラクターをプライベートとして指定する機能があり、クラス自身の外部でインスタンスを作成できないようにすることができます。 JavaScript にはこれを行うネイティブな方法はありませんが、プライベート静的フラグを使用することで実現できます。 + +```js +class PrivateConstructor { + static #isInternalConstructing = false; + + constructor() { + if (!PrivateConstructor.#isInternalConstructing) { + throw new TypeError("PrivateConstructor は構築できません"); + } + PrivateConstructor.#isInternalConstructing = false; + // More initialization logic + } + + static create() { + PrivateConstructor.#isInternalConstructing = true; + const instance = new PrivateConstructor(); + return instance; + } +} -console.log(Derived.publicStaticMethod1()); -// 42 -console.log(Derived.publicStaticMethod2()); -// TypeError: Cannot read private member #privateStaticMethod -// from an object whose class did not declare it +new PrivateConstructor(); // TypeError: PrivateConstructor は構築できません +PrivateConstructor.create(); // PrivateConstructor {} ``` ## 仕様書 -{{Specifications("javascript.classes")}} +{{Specifications}} ## ブラウザーの互換性 @@ -236,8 +385,10 @@ console.log(Derived.publicStaticMethod2()); ## 関連情報 -- [プライベートクラス機能での作業](/ja/docs/Web/JavaScript/Guide/Working_With_Private_Class_Features) +- [クラスの使用](/ja/docs/Web/JavaScript/Guide/Using_classes)ガイド +- [クラス](/ja/docs/Web/JavaScript/Reference/Classes) - [パブリッククラスフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields) -- [The Semantics of All JS Class Elements](https://rfrn.org/~shu/2018/05/02/the-semantics-of-all-js-class-elements.html) -- [Public and private class fields](https://v8.dev/features/class-fields) - v8.dev サイトの記事 +- {{jsxref("Statements/class", "class")}} +- [Private Syntax FAQ](https://github.com/tc39/proposal-class-fields/blob/main/PRIVATE_SYNTAX_FAQ.md) (TC39 class-fields proposal) +- [The semantics of all JS class elements](https://rfrn.org/~shu/2018/05/02/the-semantics-of-all-js-class-elements.html) (Shu-yu Guo, 2018) +- [Public and private class fields](https://v8.dev/features/class-fields) (v8.dev, 2018) diff --git a/files/ja/web/javascript/reference/classes/public_class_fields/index.md b/files/ja/web/javascript/reference/classes/public_class_fields/index.md index a9d15d7329c215..373bd61433ff95 100644 --- a/files/ja/web/javascript/reference/classes/public_class_fields/index.md +++ b/files/ja/web/javascript/reference/classes/public_class_fields/index.md @@ -1,261 +1,243 @@ --- title: パブリッククラスフィールド slug: Web/JavaScript/Reference/Classes/Public_class_fields +l10n: + sourceCommit: 41cddfdaeed4a73fb8234c332150df8e54df31e9 --- -{{JsSidebar("Classes")}} +{{jsSidebar("Classes")}} パブリックフィールドは、静的なものもインスタンスのものも書き込み可能、列挙可能、構成可能です。そのため、プライベートなものとは異なり、プロトタイプの継承に参加します。 ## 構文 -```js -class ClassWithInstanceField { - instanceField = "instance field"; -} - -class ClassWithStaticField { - static staticField = "static field"; -} - -class ClassWithPublicInstanceMethod { - publicMethod() { - return "hello world"; - } +```js-nolint +class ClassWithField { + instanceField; + instanceFieldWithInitializer = "instance field"; + static staticField; + static staticFieldWithInitializer = "static field"; } ``` -## 例 +それ以外にも構文上の制約があります。 -### パブリック静的フィールド +- 静的プロパティ(フィールドまたはメソッド)の名前を `prototype` とすることはできません。 +- クラスフィールド(静的またはインスタンス)の名前を `constructor` とすることはできません。 -パブリック静的フィールドは、フィールドがすべてのクラスインスタンスではなく、クラスごとに一つだけ存在するようにしたい場合に役立ちます。これは、キャッシュや固定設定、その他のインスタンス間で複製する必要のないデータなどに便利です。 +## 解説 -パブリック静的フィールドは、`static` キーワードを使用して宣言されます。これらは、クラスの評価時に{{jsxref("Global_Objects/Object/defineProperty", "Object.defineProperty()")}} を使用してコンストラクターに追加され、その後はコンストラクターからアクセスします。 +このページでは、パブリックインスタンスフィールドについて詳しく紹介します。 -```js -class ClassWithStaticField { - static staticField = "static field"; -} +- パブリック静的フィールドについては、 [`static`](/ja/docs/Web/JavaScript/Reference/Classes/static) を参照してください。 +- プライベートフィールドについては、[プライベートプロパティ](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties)を参照してください。 +- パブリックメソッドについては、[メソッド定義](/ja/docs/Web/JavaScript/Reference/Functions/Method_definitions)を参照してください。 +- パブリックアクセサーについては、[ゲッター](/ja/docs/Web/JavaScript/Reference/Functions/get)および[セッター](/ja/docs/Web/JavaScript/Reference/Functions/set)を参照してください。 -console.log(ClassWithStaticField.staticField); -// 期待される結果: "static field" -``` +パブリックインスタンスフィールドは、作成されたクラスのすべてのインスタンスに存在します。パブリックフィールドを宣言することで、そのフィールドが常に存在することを保証することができ、クラスの定義がより自己文書化されます。 -初期化子のないフィールドは `undefined` に初期化されます。 +パブリックインスタンスフィールドは、基底クラスではコンストラクターが実行される前の時点でインスタンスに追加され、サブクラスでは `super()` が返された直後に追加されます。初期化子のないフィールドは `undefined` に初期化されます。プロパティと同様に、フィールド名は計算することができます。 ```js -class ClassWithStaticField { - static staticField; +const PREFIX = "prefix"; + +class ClassWithField { + field; + fieldWithInitializer = "instance field"; + [`${PREFIX}Field`] = "prefixed field"; } -console.assert(ClassWithStaticField.hasOwnProperty("staticField")); -console.log(ClassWithStaticField.staticField); -// 期待される結果: "undefined" +const instance = new ClassWithField(); +console.log(Object.hasOwn(instance, "field")); // true +console.log(instance.field); // undefined +console.log(instance.fieldWithInitializer); // "instance field" +console.log(instance.prefixField); // "prefixed field" ``` -パブリック静的フィールドはサブクラスでは再初期化されませんが、プロトタイプチェーンを介してアクセスすることができます。 +計算されたフィールド名は、[クラス定義時点](/ja/docs/Web/JavaScript/Reference/Classes#evaluation_order)で一度だけ評価されます。これは、クラスごとに常に固定された一連のフィールド名を持つことを意味しており、2つのインスタンスが計算された名前によって異なるフィールド名を持つことはありません。計算式の `this` 値はクラス定義を囲む `this` であり、クラス名を参照することは {{jsxref("ReferenceError")}} となります。この式では {{jsxref("Operators/await", "await")}} と {{jsxref("Operators/yield", "yield")}} は期待通りに動作します。 ```js -class ClassWithStaticField { - static baseStaticField = "base field"; -} - -class SubClassWithStaticField extends ClassWithStaticField { - static subStaticField = "sub class field"; +class C { + [Math.random()] = 1; } -console.log(SubClassWithStaticField.subStaticField); -// 期待される結果: "sub class field" - -console.log(SubClassWithStaticField.baseStaticField); -// 期待される結果: "base field" +console.log(new C()); +console.log(new C()); +// Both instances have the same field name ``` -フィールドを初期化するとき、`this` はそのクラスのコンストラクターを参照します。名前で参照することもできますし、`super` を使用するとスーパークラスのコンストラクターを (存在する場合は) 取得することもできます。 +フィールド初期化子の中では、[`this`](/ja/docs/Web/JavaScript/Reference/Operators/this)は作成中のクラスインスタンスを参照し、[`super`](/ja/docs/Web/JavaScript/Reference/Operators/super)は基底クラスの `prototype` プロパティを参照します。このプロパティには基底クラスのインスタンスメソッドが格納されていますが、インスタンスフィールドは格納されていません。 ```js -class ClassWithStaticField { - static baseStaticField = "base static field"; - static anotherBaseStaticField = this.baseStaticField; - - static baseStaticMethod() { - return "base static method output"; +class Base { + baseField = "base field"; + anotherBaseField = this.baseField; + baseMethod() { + return "base method output"; } } -class SubClassWithStaticField extends ClassWithStaticField { - static subStaticField = super.baseStaticMethod(); +class Derived extends Base { + subField = super.baseMethod(); } -console.log(ClassWithStaticField.anotherBaseStaticField); -// 期待される結果: "base static field" +const base = new Base(); +const sub = new Derived(); -console.log(SubClassWithStaticField.subStaticField); -// 期待される結果: "base static method output" -``` - -### パブリックインスタンスフィールド +console.log(base.anotherBaseField); // "base field" -パブリックインスタンスフィールドは、作成されたクラスのすべてのインスタンスに存在します。パブリックフィールドを宣言することで、フィールドが常に存在していることを確認でき、クラス定義がより自己文書化されます。 +console.log(sub.subField); // "base method output" +``` -パブリックインスタンスフィールドは、基底クラスの構築時 (コンストラクター本体が実行される前)、またはサブクラスの `super()` が返された直後のいずれかに {{jsxref("Global_Objects/Object/defineProperty", "Object.defineProperty()")}} で追加されます。 +フィールドの初期化式は、新しいインスタンスが作成されるたびに評価されます。(`this` の値はインスタンスごとに異なるので、初期化式はインスタンス固有のプロパティにアクセスすることができます。) ```js -class ClassWithInstanceField { - instanceField = "instance field"; +class C { + obj = {}; } -const instance = new ClassWithInstanceField(); -console.log(instance.instanceField); -// 期待される結果: "instance field" +const instance1 = new C(); +const instance2 = new C(); +console.log(instance1.obj === instance2.obj); // false ``` -初期化子のないフィールドは `undefined` に初期化されます。 +式は同期的に評価されます。初期化式で {{jsxref("Operators/await", "await")}} や {{jsxref("Operators/yield", "yield")}} を使用することはできません。(初期化式は暗黙的に関数に包まれていると考えてください。) + +クラスのインスタンスフィールドは、それぞれのコンストラクターが実行される前に追加されるので、コンストラクター内でフィールドの値にアクセスすることができます。しかし、派生クラスのインスタンスフィールドは `super()` を返した後に定義されるので、基底クラスのコンストラクターが派生クラスのフィールドにアクセスすることはできません。 ```js -class ClassWithInstanceField { - instanceField; +class Base { + constructor() { + console.log("Base constructor:", this.field); + } +} + +class Derived extends Base { + field = 1; + constructor() { + super(); + console.log("Derived constructor:", this.field); + this.field = 2; + } } -const instance = new ClassWithInstanceField(); -console.assert(instance.hasOwnProperty("instanceField")); -console.log(instance.instanceField); -// 期待される結果: "undefined" +const instance = new Derived(); +// Base constructor: undefined +// Derived constructor: 1 +console.log(instance.field); // 2 ``` -プロパティと同様に、フィールド名を計算することができます。 +フィールドは 1 つずつ追加されます。フィールド初期化子はその上のフィールドの値を参照することはできますが、その下のフィールドの値を参照することはできません。インスタンスメソッドと静的メソッドはすべて事前に追加され、アクセスすることができますが、初期化されているフィールドより下のフィールドを参照している場合、呼び出すと期待した動作をしないことがあります。 ```js -const PREFIX = "prefix"; - -class ClassWithComputedFieldName { - [`${PREFIX}Field`] = "prefixed field"; +class C { + a = 1; + b = this.c; + c = this.a + 1; + d = this.c + 1; } -const instance = new ClassWithComputedFieldName(); -console.log(instance.prefixField); -// 期待される結果: "prefixed field" +const instance = new C(); +console.log(instance.d); // 3 +console.log(instance.b); // undefined ``` -フィールドを初期化する場合、`this` は構築中のクラスインスタンスを参照します。パブリックインスタンスメソッドと同じように、サブクラスにいる場合は `super` を使ってスーパークラスのプロトタイプにアクセスできます。 +> **メモ:** これは[プライベートフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties)ではより重要です。初期化されていないプライベートフィールドにアクセスすると、たとえプライベートフィールドが下記で宣言されていても {{jsxref("TypeError")}} が発生するからです。(プライベートフィールドが宣言されていない場合は、早期に {{jsxref("SyntaxError")}} となります。) + +クラスフィールドは [`[[DefineOwnProperty]]`](/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty) の意味づけ(本質的には {{jsxref("Object.defineProperty()")}})を使用して追加されるため、派生クラスのフィールド宣言は基底クラスのセッターを呼び出すことはありません。この動作は、コンストラクターで `this.field = …` を使用するのとは異なります。 ```js -class ClassWithInstanceField { - baseInstanceField = "base field"; - anotherBaseInstanceField = this.baseInstanceField; - baseInstanceMethod() { - return "base method output"; +class Base { + set field(val) { + console.log(val); } } -class SubClassWithInstanceField extends ClassWithInstanceField { - subInstanceField = super.baseInstanceMethod(); +class DerivedWithField extends Base { + field = 1; } -const base = new ClassWithInstanceField(); -const sub = new SubClassWithInstanceField(); +const instance = new DerivedWithField(); // No log -console.log(base.anotherBaseInstanceField); -// 期待される結果: "base field" - -console.log(sub.subInstanceField); -// 期待される結果: "base method output" -``` - -### パブリックメソッド - -#### パブリック静的メソッド - -**`static`** キーワードで、クラスの静的メソッドを定義します。静的メソッドは、クラスのインスタンスでは呼び出されません。代わりに、クラス自体から呼び出されます。これらの多くは、オブジェクトを作成したり、クローンを作成したりするようなユーティリティ関数です。 - -```js -class ClassWithStaticMethod { - static staticMethod() { - return "static method has been called."; +class DerivedWithConstructor extends Base { + constructor() { + super(); + this.field = 1; } } -console.log(ClassWithStaticMethod.staticMethod()); -// 期待される結果: "static method has been called." +const instance2 = new DerivedWithConstructor(); // Logs 1 ``` -静的メソッドは、クラスの評価時に {{jsxref("Global_Objects/Object/defineProperty", "Object.defineProperty()")}} を使用してクラスのコンストラクターに追加されます。これらのメソッドは書き込み可能、列挙不可、設定可能です。 +> **メモ:** クラスフィールドの仕様が最終的に `[[DefineOwnProperty]]` の意味づけで決定される以前は、 [Babel](https://babeljs.io/) や [tsc](https://www.typescriptlang.org/) を含むほとんどのトランスパイラーはクラスフィールドを `DerivedWithConstructor` 形式に変換しており、クラスフィールドが標準化された後に微妙なバグが発生していました。 -#### パブリックインスタンスメソッド +## 例 -パブリックインスタンスメソッドはその名の通り、クラスインスタンスで利用できるメソッドです。 +### クラスフィールドの使用 -```js -class ClassWithPublicInstanceMethod { - publicMethod() { - return "hello world"; - } -} +クラスフィールドはコンストラクターの引数に依存できないので、フィールド初期化子は通常、インスタンスごとに同じ値として評価されます({{jsxref("Date.now()")}} やオブジェクト初期化子のように、同じ式が時刻ごとに異なる値として評価できる場合を除きます)。 -const instance = new ClassWithPublicInstanceMethod(); -console.log(instance.publicMethod()); -// 期待される結果: "hello world" +```js example-bad +class Person { + name = nameArg; // nameArg はコンストラクターのスコープ外 + constructor(nameArg) {} +} ``` -パブリックインスタンスメソッドは、{{jsxref("Global_Objects/Object/defineProperty", "Object.defineProperty()")}} を使用して、クラスの評価時にクラスプロトタイプに追加されます。これらのメソッドは書き込み可能、列挙不可、設定可能です。 - -ジェネレーター関数、非同期関数、非同期ジェネレーター関数を利用することができます。 - -```js -class ClassWithFancyMethods { - *generatorMethod() {} - async asyncMethod() {} - async *asyncGeneratorMethod() {} +```js example-good +class Person { + // すべてのインスタンスが同じ名前になる + name = "Dragomir"; } ``` -インスタンスメソッドの中では、`this` はインスタンス自体を指します。サブクラスでは、`super` を使用してスーパークラスのプロトタイプにアクセスし、そのスーパークラスからメソッドを呼び出すことができます。 +しかし、空であってもクラスフィールドを宣言することは有益です。フィールドの存在を示すことで、人間の読者だけでなく型チェッカーもクラスの形状を静的に分析できるようになるからです。 ```js -class BaseClass { - msg = "hello world"; - basePublicMethod() { - return this.msg; +class Person { + name; + age; + constructor(name, age) { + this.name = name; + this.age = age; } } +``` -class SubClass extends BaseClass { - subPublicMethod() { - return super.basePublicMethod(); +上のコードは冗長なように見えますが、 `this` が動的に変更される場合を考えてみましょう。明示的にフィールド宣言を行うと、インスタンスに必ず存在するフィールドが明確になります。 + +```js +class Person { + name; + age; + constructor(properties) { + Object.assign(this, properties); } } - -const instance = new SubClass(); -console.log(instance.subPublicMethod()); -// 期待される結果: "hello world" ``` -ゲッターとセッターは、クラスのプロパティにバインドする特別なメソッドで、そのプロパティがアクセスされたり設定されたりしたときに呼び出されます。[get](/ja/docs/Web/JavaScript/Reference/Functions/get) および [set](/ja/docs/Web/JavaScript/Reference/Functions/set) 構文を使用して、パブリックインスタンスのゲッターまたはセッターを宣言します。 +初期化子は基底クラスが実行された後に評価されるので、基底クラスのコンストラクターで作成したプロパティにアクセスすることができます。 ```js -class ClassWithGetSet { - #msg = "hello world"; - get msg() { - return this.#msg; - } - set msg(x) { - this.#msg = `hello ${x}`; +class Person { + name; + age; + constructor(name, age) { + this.name = name; + this.age = age; } } -const instance = new ClassWithGetSet(); -console.log(instance.msg); -// 期待される結果: "hello world" +class Professor extends Person { + name = `Professor ${this.name}`; +} -instance.msg = "cake"; -console.log(instance.msg); -// 期待される結果: "hello cake" +console.log(new Professor("Radev", 54).name); // "Professor Radev" ``` ## 仕様書 -{{Specifications("javascript.classes")}} +{{Specifications}} ## ブラウザーの互換性 @@ -263,7 +245,9 @@ console.log(instance.msg); ## 関連情報 -- [The - Semantics of All JS Class Elements](https://rfrn.org/~shu/2018/05/02/the-semantics-of-all-js-class-elements.html) -- [Public and private class fields](https://v8.dev/features/class-fields) - v8.dev site の記事 +- [クラスの使用](/ja/docs/Web/JavaScript/Guide/Using_classes)ガイド +- [クラス](/ja/docs/Web/JavaScript/Reference/Classes) +- [プライベートプロパティ](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties) +- {{jsxref("Statements/class", "class")}} +- [The semantics of all JS class elements](https://rfrn.org/~shu/2018/05/02/the-semantics-of-all-js-class-elements.html) (Shu-yu Guo, 2018) +- [Public and private class fields](https://v8.dev/features/class-fields) (v8.dev, 2018) diff --git a/files/ja/web/javascript/reference/classes/static/index.md b/files/ja/web/javascript/reference/classes/static/index.md index 6e589ccc7f135a..8775c4969b2420 100644 --- a/files/ja/web/javascript/reference/classes/static/index.md +++ b/files/ja/web/javascript/reference/classes/static/index.md @@ -7,22 +7,94 @@ slug: Web/JavaScript/Reference/Classes/static **`static`** キーワードは、クラスに静的メソッドや静的プロパティを定義します。静的メソッドも静的プロパティもクラスのインスタンスからは呼び出されません。その代わりに、クラスそのものから呼び出されます。静的メソッドは多くの場合、オブジェクトの生成や複製を行う関数などのユーティリティ関数です。静的プロパティはキャッシュ、固定的な構成、その他の各インスタンスに複製する必要のないデータです。 -{{EmbedInteractiveExample("pages/js/classes-static.html")}} +静的メソッドは多くの場合、オブジェクトの生成や複製を行う関数などのユーティリティ関数です。静的プロパティはキャッシュ、固定的な構成、その他の各インスタンスに複製する必要のないデータです。 + +> **メモ:** クラスの文脈において、 MDN Web Docs のコンテンツではプロパティと[フィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields)という用語を同等のものとして使用しています。 + +{{EmbedInteractiveExample("pages/js/classes-static.html", "taller")}} ## 構文 +```js-nolint +class ClassWithStatic { + static staticField; + static staticFieldWithInitializer = value; + static staticMethod() { + // … + } +} +``` + +それ以外にも構文上の制約があります。 + +- 静的プロパティ(フィールドまたはメソッド)の名前を `prototype` とすることはできません。 +- クラスフィールド(静的またはインスタンス)の名前を `constructor` とすることはできません。 + +## 解説 + +このページでは、静的メソッド、静的アクセサ、静的フィールドを含む、クラスのパブリック静的プロパティを紹介します。 + +- プライベートな静的機能については、[プライベートプロパティ](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties)を参照してください。 +- インスタンスの機能については、[メソッド定義](/ja/docs/Web/JavaScript/Reference/Functions/Method_definitions)、[ゲッター](/ja/docs/Web/JavaScript/Reference/Functions/get)、[セッター](/ja/docs/Web/JavaScript/Reference/Functions/set)、[パブリッククラスフィールド](/ja/docs/Web/JavaScript/Reference/Classes/Public_class_fields)を参照してください。 + +パブリック静的機能は、`static` キーワードを使用して宣言します。これらは[クラス評価時](/ja/docs/Web/JavaScript/Reference/Classes#評価の順序)に、 [`[[DefineOwnProperty]]`](/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty) の意味づけ(これは本質的に {{jsxref("Object.defineProperty()")}} です)を使用して、クラスのコンストラクターに追加されます。これらは、コンストラクターから再びアクセスします。 + +静的メソッドの多くは、インスタンスを作成したり複製したりするなどの、ユーティリティ関数です。パブリック静的フィールドは、作成するすべてのクラスインスタンスではなく、クラスごとに一つだけフィールドを存在させたい場合に有益です。これは、キャッシュや固定の構成値、 あるいはインスタンスをまたがって複製する必要のないデータなどに有益です。 + +静的フィールド名は[計算](/ja/docs/Web/JavaScript/Reference/Operators/Object_initializer#計算されたプロパティ名)できます。計算式の `this` 値はクラス定義の周囲の `this` であり、クラス名を参照すると、クラスがまだ初期化されていないため {{jsxref("ReferenceError")}} になります。この式では {{jsxref("Operators/await", "await")}} と {{jsxref("Operators/yield", "yield")}} は期待どおりに動作します。 + +静的フィールドは初期化子を持つことができます。初期化子を持たない静的フィールドは `undefined` に初期化されます。パブリック静的フィールドはサブクラスでは再初期化されませんが、プロトタイプチェーン経由でアクセスすることができます。 + ```js -static methodName() { ... } -static propertyName [= value]; +class ClassWithStaticField { + static staticField; + static staticFieldWithInitializer = "静的フィールド"; +} + +class SubclassWithStaticField extends ClassWithStaticField { + static subStaticField = "サブクラスのフィールド"; +} + +console.log(Object.hasOwn(ClassWithStaticField, "staticField")); // true +console.log(ClassWithStaticField.staticField); // undefined +console.log(ClassWithStaticField.staticFieldWithInitializer); // "静的フィールド" +console.log(SubclassWithStaticField.staticFieldWithInitializer); // "静的フィールド" +console.log(SubclassWithStaticField.subStaticField); // "サブクラスのフィールド" +``` + +フィールド初期化子では、 [`this`](/ja/docs/Web/JavaScript/Reference/Operators/this) は現在のクラス(その名前からもアクセスすることができます)を参照し、 [`super`](/ja/docs/Web/JavaScript/Reference/Operators/super) は基底クラスのコンストラクターを参照します。 + +```js +class ClassWithStaticField { + static baseStaticField = "基底クラスの静的フィールド"; + static anotherBaseStaticField = this.baseStaticField; + + static baseStaticMethod() { + return "基底クラスの静的フィールドの出力"; + } +} + +class SubClassWithStaticField extends ClassWithStaticField { + static subStaticField = super.baseStaticMethod(); +} + +console.log(ClassWithStaticField.anotherBaseStaticField); // "基底クラスの静的フィールド" +console.log(SubClassWithStaticField.subStaticField); // "基底クラスの静的フィールドの" ``` +式は同期的に評価されます。初期化子式で({{jsxref("Operators/await", "await")}} や {{jsxref("Operators/yield", "yield")}})を使用することはできません。(初期化子式は暗黙に関数に包まれていると考えてください)。 + +静的フィールド初期化子と[静的初期化ブロック](/ja/docs/Web/JavaScript/Reference/Classes/Static_initialization_blocks)は、 1 つずつ評価されます。フィールド初期化子は、それより上のフィールド値を参照することはできますが、それより下のフィールド値を参照することはできません。静的メソッドはすべて事前に追加され、アクセスすることができますが、初期化されるフィールドより下のフィールドを参照している場合、呼び出すと期待した動作をしないことがあります。 + +> **メモ:** これは[プライベート静的フィールド](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties)ではより重要です。初期化されていないプライベートフィールドにアクセスすると、たとえそのプライベートフィールドが下で宣言されていたとしても、 {{jsxref("TypeError")}} が発生するからです。(プライベートフィールドが宣言されていない場合は、早期に {{jsxref("SyntaxError")}} となります。) + ## 例 ### クラスでの静的メンバーの使用 次の例はいくつかのことを説明しています。 -1. 静的メンバー (メソッドまたはプロパティ) がクラスでどのように定義されるか +1. 静的メンバー(メソッドまたはプロパティ)がクラスでどのように定義されるか 2. 静的メンバーを持つクラスがサブクラスを作れるか 3. 静的メンバーがどう呼び出せて、どう呼び出せないか diff --git a/files/ja/web/javascript/reference/classes/static_initialization_blocks/index.md b/files/ja/web/javascript/reference/classes/static_initialization_blocks/index.md index 8a6fcce7a10d29..c26790715832fb 100644 --- a/files/ja/web/javascript/reference/classes/static_initialization_blocks/index.md +++ b/files/ja/web/javascript/reference/classes/static_initialization_blocks/index.md @@ -7,12 +7,7 @@ l10n: {{jsSidebar("Classes")}} -**静的初期化ブロック**は{{jsxref("Statements/class", "クラス")}}の特殊機能で、フィールドごとの初期化よりも柔軟に{{jsxref("Classes/static", "静的")}}プロパティを初期化できるようにします。 - -静的ブロックでは、初期化中に文を評価することができるため、(例えば)`try...catch` を含めたり、1 つの値を複数のフィールドに設定したりすることができます。 - -初期化は、現在のクラス宣言のコンテキストで実行され、プライベート状態への特権的なアクセスが可能です。 -つまり、静的ブロックは、インスタンスのプライベートフィールドを持つクラスと、同じスコープで宣言された他のクラスや関数との間で情報を共有するためにも使用できます(C++ の "friend" クラスに似ています)。 +**静的初期化ブロック**は{{jsxref("Statements/class", "クラス", "", 1)}}内で宣言されます。これは、クラスの初期化の時に評価される文を格納します。これにより、{{jsxref("Classes/static", "静的", "", 1)}}プロパティよりも柔軟な初期化ロジックが可能になり、 `try...catch` を使用したり、 1 つの値から複数のフィールドを設定したりすることができます。初期化は現在のクラス宣言のコンテキストで実行され、プライベートプロパティにアクセスすることができます。つまり、インスタンスのプライベートフィールドを持つクラスと、同じスコープで宣言された他のクラスや関数との間で情報を共有するためにも使用できます(C++ の "friend" クラスに似ています)。 {{EmbedInteractiveExample("pages/js/classes-static-initialization.html")}} @@ -28,11 +23,25 @@ class ClassWithSIB { ## 解説 +静的初期化ブロックを使わない場合、クラス宣言の後で静的メソッドを呼び出すことで、複雑な静的初期化を行うことができます。 + +```js +class MyClass { + static init() { + // プライベート静的フィールドにアクセスすることができる + } +} + +MyClass.init(); +``` + +しかし、この手法では実装の詳細(`init()` メソッド)がクラスのユーザーに公開されてしまいます。一方、クラスの外部で宣言された初期化ロジックはプライベート静的フィールドにアクセスすることはできません。静的初期化ブロックでは、任意の初期化ロジックをクラス内で宣言し、クラスの評価中に実行することができます。 + {{jsxref("Statements/class", "class")}} は、そのクラス本体に任意の数の `static {}` 初期化ブロックを置くことができます。 -これらのブロックは、宣言された順に、静的フィールド初期化子とともに評価されます。 +これらのブロックは、宣言された順に、静的フィールド初期化子とともに[評価](/ja/docs/Web/JavaScript/Reference/Classes#評価の順序)されます。 スーパークラスの静的初期化は、そのサブクラスの初期化よりも先に実行されます。 -静的ブロックの内部で宣言された変数のスコープは、そのブロックのローカルなものです。`static {}` 初期化ブロック内で宣言された `var`, `function`, `const`, `let` は、そのブロックのローカル変数であるため、ブロック内の `var` 宣言は巻き上げされることはありません。 +静的ブロックの内部で宣言された変数のスコープは、そのブロックのローカルなものです。ここには初期化ブロック内で宣言された `var`, `function`, `const`, `let` は、そのブロックのローカル変数であるため、ブロック内の `var` 宣言は巻き上げされることはありません。 ```js var y = "Outer y"; @@ -49,10 +58,16 @@ console.log(y); // 'Outer y' ``` 静的ブロック内の `this` は、そのクラスのコンストラクター オブジェクトを参照します。 -`super.<プロパティ>` を使用して、スーパークラスのプロパティにアクセスすることができます。 -ただし、クラスの静的初期化ブロック内で {{jsxref("Operators/super", "super()")}} を呼び出したり、クラスのコンストラクター関数の引数にアクセスしようとするのは構文エラーであることに注意してください。 +`super.プロパティ` を使用して、スーパークラスの静的プロパティにアクセスすることができます。 +ただし、クラスの静的初期化ブロック内で {{jsxref("Operators/super", "super()")}} を呼び出したり、 {{jsxref("Functions/arguments", "arguments")}} オブジェクトを使用したりするのは構文エラーであることに注意してください。 -静的初期化ブロックのスコープは、クラス本体の字句スコープに入れ子になっており、クラスのプライベートなインスタンス変数にアクセスすることができます。 +式は同期的に評価されます。初期化子式で({{jsxref("Operators/await", "await")}} や {{jsxref("Operators/yield", "yield")}})を使用することはできません。(初期化子式は暗黙に関数に包まれていると考えてください)。 + +静的ブロックのスコープは、クラス本体の字句スコープの中で入れ子になり、構文エラーを発生させることなく、クラス内で宣言された[プライベート名](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties)にアクセスすることができます。 + +[静的フィールド](/ja/docs/Web/JavaScript/Reference/Classes/static)初期化子と静的初期化ブロックは、 1 つずつ評価されます。フィールド初期化子は、それより上のフィールド値を参照することはできますが、それより下のフィールド値を参照することはできません。静的メソッドはすべて事前に追加され、アクセスすることができますが、初期化されるフィールドより下のフィールドを参照している場合、呼び出すと期待した動作をしないことがあります。 + +> **メモ:** これは[プライベート静的フィールド](/ja/docs/Web/JavaScript/Reference/Classes/Private_properties)ではより重要です。初期化されていないプライベートフィールドにアクセスすると、たとえそのプライベートフィールドが下で宣言されていたとしても、 {{jsxref("TypeError")}} が発生するからです。(プライベートフィールドが宣言されていない場合は、早期に {{jsxref("SyntaxError")}} となります。) 静的初期化ブロックはデコレーターを持つことができません(クラス自身は持つことができます)。 @@ -65,22 +80,19 @@ console.log(y); // 'Outer y' ```js class MyClass { - static field1 = console.log("field1 called"); + static field1 = console.log("static field1"); static { - console.log("Class static block #1 called"); + console.log("static block1"); } - static field2 = console.log("field2 called"); + static field2 = console.log("static field2"); static { - console.log("Class static block #2 called"); + console.log("static block2"); } } - -/* -> "field1 called" -> "Class static block #1 called" -> "field2 called" -> "Class static block #2 called" -*/ +// 'static field1' +// 'static block1' +// 'static field2' +// 'static block2' ``` スーパークラスの静的な初期化は、サブクラスの初期化よりも最初に行われることに注意してください。 @@ -88,35 +100,36 @@ class MyClass { ### this と super の使用 静的ブロック内の `this` は、そのクラスのコンストラクター オブジェクトを参照します。 -このコードは、パブリックな静的フィールドにアクセスする方法を示しています。 +このコードは、パブリック静的フィールドにアクセスする方法を示しています。 ```js class A { - static field = "A static field"; + static field = "static field"; static { console.log(this.field); } } +// 'static field' ``` -[`super.property`](/ja/docs/Web/JavaScript/Reference/Operators/super) を `static` ブロックの中で使用すると、スーパークラスのプロパティを参照することができます。 -下記で示すように、静的プロパティを含めるために使用します。 +[`super.property`](/ja/docs/Web/JavaScript/Reference/Operators/super) 構文を `static` ブロックの中で使用すると、スーパークラスの静的プロパティを参照することができます。 ```js class A { - static fieldA = "A.fieldA"; + static field = "static field"; } + class B extends A { static { - console.log(super.fieldA); - // 'A.fieldA' + console.log(super.field); } } +// 'static field' ``` -### プライベートフィールドへのアクセス +### プライベートプロパティへのアクセス -下記は、クラス外のオブジェクトからクラスのプライベートオブジェクトにアクセスを許可する例です([v8.dev blog](https://v8.dev/features/class-static-initializer-blocks#access-to-private-fields)より)。 +下記は、クラス外のオブジェクトからクラスのプライベートインスタンスフィールドにアクセスを許可する例です([v8.dev blog](https://v8.dev/features/class-static-initializer-blocks#access-to-private-fields)より)。 ```js let getDPrivateField; @@ -131,15 +144,9 @@ class D { } } -console.log(getDPrivateField(new D("private"))); // private +console.log(getDPrivateField(new D("private"))); // 'private' ``` -### 代替策 - -ES13 以前で、より複雑な静的初期化を行うには、他のプロパティの後に静的メソッドが呼び出されるようにするか、初期化タスクを実行するクラスの外部メソッドを設定することで実現できるかもしれません。 - -どちらの場合も、手法はあまり洗練されておらず、クラスのプライベートメソッドへのアクセスも許可されません。 - ## 仕様書 {{Specifications}} @@ -150,8 +157,9 @@ ES13 以前で、より複雑な静的初期化を行うには、他のプロパ ## 関連情報 -- [Class static initialization blocks](https://v8.dev/features/class-static-initializer-blocks) (v8.dev blog) -- [ES2022 feature: class static initialization blocks](https://2ality.com/2021/09/class-static-block.html) (2ality.com blog) +- [クラスの使用](/ja/docs/Web/JavaScript/Guide/Using_classes)ガイド - [クラス](/ja/docs/Web/JavaScript/Reference/Classes) -- {{jsxref("Operators/super", "super()")}} -- [Object.prototype.constructor](/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor) +- {{jsxref("Classes/static", "static")}} +- {{jsxref("Statements/class", "class")}} +- [Class static initialization blocks](https://v8.dev/features/class-static-initializer-blocks) (v8.dev blog) +- [ES2022 feature: class static initialization blocks](https://2ality.com/2021/09/class-static-block.html) (Dr. Axel Rauschmayer, 2021)