クラスの基本構文

クラスの起源

JavaScript 言語では、インスタンス オブジェクトを生成する従来の方法はコンストラクターを使用します。以下に例を示します。

関数点(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = 新しいポイント(1, 2);

上記の記述方法は、従来のオブジェクト指向言語 (C++ や Java など) とは大きく異なるため、この言語を初めて使用するプログラマーは簡単に混乱する可能性があります。

ES6 は、従来の言語に近い記述方法を提供し、オブジェクトのテンプレートとしてクラスの概念を導入します。クラスは「class」キーワードを通じて定義できます。

基本的に、ES6 の「クラス」は単なる構文上の糖衣と見なすことができ、その機能のほとんどは ES5 で実現できます。新しい「クラス」の記述方法により、オブジェクト プロトタイプの記述方法がより明確になり、よりオブジェクト指向プログラミングに近くなります。ただの文法。上記のコードは、以下に示すように、ES6 の「class」を使用して書き換えられます。

クラスポイント{
  コンストラクター(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

上記のコードは「クラス」を定義しており、その中にコンストラクター メソッドである constructor() メソッドがあり、this キーワードがインスタンス オブジェクトを表していることがわかります。クラスを記述するこの新しい方法は、この章の冒頭にある ES5 コンストラクター Point と本質的に同じです。

コンストラクター メソッドに加えて、Point クラスは toString() メソッドも定義します。 toString() メソッドを定義する場合、先頭に function キーワードを追加する必要はなく、関数定義を直接記述するだけであることに注意してください。さらに、メソッドをカンマで区切る必要はありません。カンマで区切らないと、エラーが報告されます。

ES6 クラスは、コンストラクターを記述する別の方法とみなすことができます。

クラスポイント{
  // ...
}

typeof Point // "関数"
Point === Point.prototype.constructor // true

上記のコードは、クラスのデータ型が関数であり、クラス自体がコンストラクターを指していることを示しています。

これを使用するときは、クラス上で new コマンドを直接使用します。これはコンストラクターの使用法とまったく同じです。

クラスバー{
  doStuff() {
    console.log('もの');
  }
}

const b = 新しい Bar();
b.doStuff() // 「もの」

コンストラクターの prototype 属性は ES6 の「クラス」に引き続き存在します。実際、クラスのすべてのメソッドは、クラスの prototype 属性で定義されます。

クラスポイント{
  コンストラクター() {
    // ...
  }

  toString() {
    // ...
  }

  toValue() {
    // ...
  }
}

// と同等

ポイント.プロトタイプ = {
  コンストラクター() {}、
  toString() {}、
  toValue() {}、
};

上記のコードでは、実際には 3 つのメソッド constructor()toString()toValue()Point.prototype 上に定義されています。

したがって、クラスのインスタンスでメソッドを呼び出すことは、実際にはプロトタイプでメソッドを呼び出すことになります。

クラス B {}
const b = 新しい B();

b.constructor === B.prototype.constructor // true

上記のコードでは、b はクラス B のインスタンスであり、その constructor() メソッドはクラス B のプロトタイプの constructor() メソッドです。

クラスのメソッドは prototype オブジェクト上で定義されるため、クラスの新しいメソッドを prototype オブジェクト上に追加できます。 Object.assign() メソッドは、複数のメソッドをクラスに一度に追加する便利な方法です。

クラスポイント{
  コンストラクタ(){
    // ...
  }
}

Object.assign(Point.prototype, {
  toString(){}、
  toValue(){}
});

「prototype」オブジェクトの「constructor」プロパティは「クラス」自体を直接指しており、これは ES5 の動作と一致しています。

Point.prototype.constructor === ポイント // true

さらに、クラス内で定義されたすべてのメソッドは列挙可能ではありません。

クラスポイント{
  コンストラクター(x, y) {
    // ...
  }

  toString() {
    // ...
  }
}

オブジェクト.キー(ポイント.プロトタイプ)
//[]
Object.getOwnPropertyNames(Point.prototype)
// ["コンストラクター","toString"]

上記のコードでは、toString() メソッドは Point クラスの内部で定義されたメソッドであり、列挙可能ではありません。これは ES5 の動作と矛盾します。

var ポイント = 関数 (x, y) {
  // ...
};

Point.prototype.toString = function () {
  // ...
};

オブジェクト.キー(ポイント.プロトタイプ)
// ["toString"]
Object.getOwnPropertyNames(Point.prototype)
// ["コンストラクター","toString"]

上記のコードは ES5 で書かれており、toString() メソッドは列挙可能です。

コンストラクター() メソッド

constructor() メソッドは、クラスのデフォルトのメソッドです。このメソッドは、オブジェクト インスタンスが new コマンドによって生成されるときに自動的に呼び出されます。クラスには constructor() メソッドが必要です。明示的に定義されていない場合は、デフォルトで空の constructor() メソッドが追加されます。

クラスポイント{
}

// と同等
クラスポイント{
  コンストラクター() {}
}

上記のコードでは、空のクラス Point が定義されており、JavaScript エンジンは空の constructor() メソッドをそれに自動的に追加します。

constructor() メソッドはデフォルトでインスタンス オブジェクト (すなわち this) を返しますが、別のオブジェクトを返すように指定することもできます。

クラス Foo {
  コンストラクター() {
    戻り値 Object.create(null);
  }
}

新しい Foo() Foo のインスタンス
// 間違い

上記のコードでは、constructor() 関数は新しいオブジェクトを返します。その結果、インスタンス オブジェクトは Foo クラスのインスタンスではありません。

クラスは「new」を使用して呼び出す必要があります。そうしないと、エラーが報告されます。これは、「new」なしで実行できる通常のコンストラクターとの大きな違いです。

クラス Foo {
  コンストラクター() {
    戻り値 Object.create(null);
  }
}

ふー()
// TypeError: クラス コンストラクター Foo は 'new' なしでは呼び出せません

クラスのインスタンス

クラスのインスタンスを生成する方法は ES5 とまったく同じで、「new」コマンドを使用します。前にも述べたように、new を追加するのを忘れて関数のように Class() を呼び出すとエラーが報告されます。

クラスポイント{
  // ...
}

// エラーを報告する
var point = ポイント(2, 3);

// 正しい
var point = 新しいポイント(2, 3);

クラスのプロパティとメソッドは、それ自体で明示的に定義されていない限り (つまり、「this」オブジェクトで定義されている場合)、プロトタイプで定義されています (つまり、「クラス」で定義されています)。

クラスポイント{
  コンストラクター(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

var point = 新しいポイント(2, 3);

point.toString() // (2, 3)

point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

上記のコードでは、xy は両方ともインスタンス オブジェクト point 自体のプロパティであるため (これらは this オブジェクトで定義されているため)、hasOwnProperty() メソッドは true を返します。 toString() これはプロトタイプ オブジェクトのプロパティであるため (Point クラスで定義されているため)、hasOwnProperty() メソッドは false を返します。これらは ES5 の動作と一致しています。

ES5 と同様、クラスのすべてのインスタンスはプロトタイプ オブジェクトを共有します。

var p1 = 新しいポイント(2,3);
var p2 = 新しいポイント(3,2);

p1.__proto__ === p2.__proto__
//真実

上記のコードでは、p1p2 は両方とも Point のインスタンスであり、それらのプロトタイプは両方とも Point.prototype であるため、__proto__ 属性は同じです。

これは、インスタンスの proto 属性を通じてメソッドを「クラス」に追加できることも意味します。

__proto__ は言語自体の機能ではありません。このプライベート属性は、現在多くの最新ブラウザの JS エンジンで提供されていますが、使用することは推奨されていません。環境への依存を避けるためのプロパティ。運用環境では、Object.getPrototypeOf() メソッドを使用してインスタンス オブジェクトのプロトタイプを取得し、そのプロトタイプにメソッド/プロパティを追加できます。

var p1 = 新しいポイント(2,3);
var p2 = 新しいポイント(3,2);

p1.__proto__.printName = function () { return 'おっと' };

p1.printName() // 「おっと」
p2.printName() // 「おっと」

var p3 = 新しいポイント(4,2);
p3.printName() // 「おっと」

上記のコードは、p1 のプロトタイプに printName() メソッドを追加します。 p1 のプロトタイプは p2 のプロトタイプであるため、p2 もこのメソッドを呼び出すことができます。さらに、新しく作成されたインスタンス p3 もこのメソッドを呼び出すことができます。これは、インスタンスの __proto__ 属性を使用してプロトタイプをオーバーライドすることは細心の注意を払って行う必要があり、「クラス」の元の定義が変更され、すべてのインスタンスに影響を与えるため推奨されないことを意味します。

インスタンス属性の新しい書き方

ES2022 は、クラス インスタンス属性を記述する新しい方法を指定しています。インスタンス プロパティは、constructor() メソッドの this だけでなく、クラス内のトップレベルでも定義できるようになりました。

//原文
クラス IncreasingCounter {
  コンストラクター() {
    this._count = 0;
  }
  値を取得() {
    console.log('現在の値を取得しています!');
    これを返します。_count;
  }
  インクリメント() {
    this._count++;
  }
}

上記の例では、インスタンス属性 _countconstructor() メソッドの this の上に定義されています。

新しい記述方法では、この属性はクラスのトップレベルでも定義でき、他のすべては変更されません。

クラス IncreasingCounter {
  _カウント = 0;
  値を取得() {
    console.log('現在の値を取得しています!');
    これを返します。_count;
  }
  インクリメント() {
    this._count++;
  }
}

上記のコードでは、インスタンス属性 _count と値関数 value() および increment() メソッドは同じレベルにあります。このとき、instance 属性の前に this を追加する必要はありません。

新しい表記法で定義されるプロパティは、インスタンス オブジェクト自体のプロパティであり、インスタンス オブジェクトのプロトタイプで定義されるものではないことに注意してください。

この新しい書き方の利点は、インスタンス オブジェクト自体のすべての属性がクラスの先頭で定義されているため、見た目がすっきりし、このクラスがどのようなインスタンス属性を持っているかが一目でわかることです。

クラス foo {
  バー = 'こんにちは';
  baz = '世界';

  コンストラクター() {
    // ...
  }
}

上記のコードから、「foo」クラスには 2 つのインスタンス属性があることが一目でわかります。また、比較的簡潔に書くことができます。

値関数(getter)と値格納関数(setter)

ES5 と同様に、「クラス」内で get および set キーワードを使用して、特定の属性のストレージ関数と値関数を設定し、属性のアクセス動作をインターセプトできます。

クラス MyClass {
  コンストラクター() {
    // ...
  }
  プロパティを取得() {
    「ゲッター」を返します。
  }
  set prop() {
    console.log('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// セッター: 123

インスタントプロップ
// 'ゲッター'

上記のコードでは、「prop」属性に対応するストレージ関数と値関数があるため、代入と読み取りの動作がカスタマイズされています。

storage関数とvalue関数はプロパティのDescriptorオブジェクトに設定されます。

クラス CustomHTMLElement {
  コンストラクター(要素) {
    this.element = 要素;
  }

  gethtml() {
    this.element.innerHTML を返します。
  }

  set html() {
    this.element.innerHTML = 値;
  }
}

var 記述子 = Object.getOwnPropertyDescriptor(
  CustomHTMLElement.prototype、「html」
);

記述子の「get」 // true
記述子の「set」 // true

上記のコードでは、storage 関数と value 関数が html 属性の description オブジェクト上に定義されており、ES5 と完全に一致しています。

属性式

クラスの属性名には式を使用できます。

let メソッド名 = 'getArea';

クラス スクエア {
  コンストラクター(長さ) {
    // ...
  }

  [メソッド名]() {
    // ...
  }
}

上記コードでは式からSquareクラスのメソッド名getAreaを取得しています。

クラス式

関数と同様に、クラスも式を使用して定義できます。

const MyClass = クラス Me {
  getクラス名() {
    Me.name を返します。
  }
};

上記のコードは、式を使用してクラスを定義します。このクラスの名前は「Me」ですが、「Me」はクラス内でのみ使用可能であり、現在のクラスを参照することに注意してください。クラスの外では、このクラスは「MyClass」としてのみ参照できます。

let inst = new MyClass();
inst.getClassName() // 私
Me.name // ReferenceError: Me が定義されていません

上記のコードは、「Me」がクラス内でのみ定義されていることを示しています。

クラス内部で使用しない場合は「Me」を省略できます。つまり、次の形式で記述できます。

const MyClass = クラス { /* ... */ };

クラス式を使用すると、すぐに実行されるクラスを作成できます。

let person = 新しいクラス {
  コンストラクター(名前) {
    this.name = 名前;
  }

  SayName() {
    console.log(この名前);
  }
}('張三');

person.sayName(); // "張三"

上記のコードでは、「person」はすぐに実行されるクラスのインスタンスです。

静的メソッド

クラスはインスタンスのプロトタイプに相当します。クラス内で定義されたすべてのメソッドはインスタンスによって継承されます。メソッドの前に static キーワードを追加すると、そのメソッドはインスタンスによって継承されず、クラスを通じて直接呼び出されます。これは「静的メソッド」と呼ばれます。

クラス Foo {
  静的クラスメソッド() {
    「こんにちは」を返します。
  }
}

Foo.classMethod() // 'こんにちは'

var foo = 新しい Foo();
foo.classMethod()
// TypeError: foo.classMethod は関数ではありません

上記のコードでは、Foo クラスの classMethod メソッドの前に static キーワードがあり、このメソッドが静的メソッドであり、Foo クラスで直接呼び出すことができることを示しています (Foo.classMethod( )) クラス Foo のインスタンスで呼び出される代わりに。静的メソッドがインスタンスで呼び出される場合、メソッドが存在しないことを示すエラーがスローされます。

静的メソッドに this キーワードが含まれている場合、この this はインスタンスではなくクラスを参照することに注意してください。

クラス Foo {
  静的バー() {
    this.baz();
  }
  静的 baz() {
    console.log('こんにちは');
  }
  バズ() {
    console.log('世界');
  }
}

Foo.bar() // こんにちは

上記のコードでは、静的メソッド barthis.baz を呼び出します。 ここで、 thisFoo のインスタンスではなく Foo クラスを参照します。これは Foo.baz を呼び出すことと同じです。さらに、この例から、静的メソッドは非静的メソッドと同じ名前を持つことができることがわかります。

親クラスの静的メソッドはサブクラスに継承できます。

クラス Foo {
  静的クラスメソッド() {
    「こんにちは」を返します。
  }
}

クラス Bar extends Foo {
}

Bar.classMethod() // 'こんにちは'

上記のコードでは、親クラス Foo が静的メソッドを持ち、サブクラス Bar がこのメソッドを呼び出すことができます。

静的メソッドは「スーパー」オブジェクトから呼び出すこともできます。

クラス Foo {
  静的クラスメソッド() {
    「こんにちは」を返します。
  }
}

クラス Bar extends Foo {
  静的クラスメソッド() {
    super.classMethod() + ', too' を返します。
  }
}

Bar.classMethod() // 「こんにちは」

静的プロパティ

静的プロパティは、インスタンス オブジェクト (「this」) で定義されたプロパティではなく、クラス自体のプロパティ、つまり「Class.propName」を参照します。

クラス Foo {
}

Foo.prop = 1;
Foo.prop // 1

上記の記述は、Foo クラスの静的プロパティ prop を定義します。

ES6 ではクラス内には静的メソッドのみが存在し、静的属性は存在しないと明確に規定されているため、現時点ではこの書き方のみが可能です。クラスの静的属性を提供する プロポーザル が作成されました。これを記述する方法は、インスタンスの前に static キーワードを追加することです。属性。

クラス MyClass {
  静的 myStaticProp = 42;

  コンストラクター() {
    console.log(MyClass.myStaticProp); // 42
  }
}

この新しい書き込み方法により、静的属性の表現が大幅に容易になります。

//古い書き方
クラス Foo {
  // ...
}
Foo.prop = 1;

//新しい書き方
クラス Foo {
  静的プロパティ = 1;
}

上記のコードでは、古い書き方の静的プロパティはクラスの外部で定義されています。クラス全体が生成された後、静的属性が生成されます。これにより、この静的属性が無視されやすくなり、関連するコードをまとめるべきであるというコード編成の原則に準拠しなくなります。さらに、新しい記述方法は、代入処理ではなく明示的な宣言 (宣言的) であり、セマンティクスが優れています。

プライベート メソッドとプライベート プロパティ

早期の解決策

プライベートメソッドおよびプライベートプロパティは、クラス内のみにアクセスでき、外部からはアクセスできないメソッドおよびプロパティです。これは一般的な要件であり、コードのカプセル化にとって有益ですが、初期の ES6 ではこれが提供されておらず、回避策によってのみシミュレートできます。

1 つの方法は、名前で区別することです。

クラスウィジェット {

  // パブリックメソッド
  フー (バズ) {
    this._bar(バズ);
  }

  // プライベートメソッド
  _バー(バズ) {
    これを返します。snaf = baz;
  }

  // ...
}

上記のコードでは、「_bar()」メソッドの前のアンダースコアは、これが内部使用に限定されたプライベート メソッドであることを示しています。ただし、この名前付けは安全ではなく、このメソッドはクラス外から呼び出すことができます。

もう 1 つのアプローチは、クラス内のすべてのメソッドが外部から見えるため、単純にプライベート メソッドをクラスの外に移動することです。

クラスウィジェット {
  フー (バズ) {
    bar.call(this, baz);
  }

  // ...
}

関数バー(baz) {
  これを返します。snaf = baz;
}

上記のコードでは、foo はパブリック メソッドであり、bar.call(this, baz) は内部的に呼び出されます。これにより、bar() は実際には現在のクラスのプライベート メソッドになります。

もう 1 つの方法は、Symbol 値の一意性を利用して、プライベート メソッドに Symbol 値という名前を付けることです。

const bar = Symbol('bar');
const snaf = Symbol('snaf');

デフォルトクラス myClass をエクスポート{

  // パブリックメソッド
  foo(バズ) {
    これ[バー](バズ);
  }

  // プライベートメソッド
  [バー](バズ) {
    これを返す[snaf] = baz;
  }

  // ...
};

上記コードでは、「bar」と「snaf」はどちらも「Symbol」の値であり、通常では取得できないため、プライベートメソッドやプライベートプロパティの効果が得られます。しかし、絶対に不可能というわけではなく、「Reflect.ownKeys()」でも取得できます。

const inst = new myClass();

Reflect.ownKeys(myClass.prototype)
// [ 'constructor', 'foo', Symbol(bar) ]

上記のコードでも、Symbol 値の属性名はクラスの外部から取得できます。

プライベート属性の正式な書き方

ES2022 は、属性名の前に # を使用して、プライベート属性を正式に class に追加します。

クラス IncreasingCounter {
  #カウント = 0;
  値を取得() {
    console.log('現在の値を取得しています!');
    これを返します。#count;
  }
  インクリメント() {
    this.#count++;
  }
}

上記のコードでは、#count はプライベート プロパティであり、クラス (this.#count) 内でのみ使用できます。クラス外で使用するとエラーが報告されます。

const counter = new IncreasingCounter();
counter.#count // エラーレポート
counter.#count = 42 // エラーレポート

上記の例では、プライベート プロパティ #count をクラス外で読み書きすると、エラーが報告されます。

Chrome 111 以降 では、開発者ツールでプライベート プロパティをエラーなく読み書きできることに注意してください。その理由は次のとおりです。その Chrome チームは、これによりデバッグが容易になると考えています。

さらに、クラスの内部か外部かに関係なく、存在しないプライベート プロパティを読み取るとエラーが報告されます。これは、パブリック プロパティの動作とはまったく異なります。存在しないパブリック プロパティを読み取った場合、エラーは報告されず、「未定義」のみが返されます。

クラス IncreasingCounter {
  #カウント = 0;
  値を取得() {
    console.log('現在の値を取得しています!');
    return this.#myCount; // エラーレポート
  }
  インクリメント() {
    this.#count++;
  }
}

const counter = new IncreasingCounter();
counter.#myCount // エラーレポート

上記の例では、#myCount は関数の内部または外部に存在しないプライベート プロパティです。このプロパティを読み取るとエラーが発生します。

プライベート属性の属性名には # が含まれている必要があり、# が含まれていない場合は別の属性として扱われることに注意してください。

クラスポイント{
  #x;

  コンストラクター(x = 0) {
    これ。#x = +x;
  }

  get x() {
    これを返します。#x;
  }

  set x() {
    this.#x = +値;
  }
}

上記のコードでは、#x はプライベート プロパティであり、このプロパティは Point クラスの外部から読み取ることはできません。ポンド記号「#」は属性名の一部であるため、「#」と一緒に使用する必要があります。つまり、「#x」と「x」は 2 つの異なる属性です。

この記述方法は、プライベート プロパティを記述するだけでなく、プライベート メソッドを記述するためにも使用できます。

クラス Foo {
  #a;
  #b;
  コンストラクター(a, b) {
    これ。#a = a;
    これ。#b = b;
  }
  #sum() {
    this.#a + this.#b を返します。
  }
  printSum() {
    console.log(this.#sum());
  }
}

上の例では、#sum() がプライベート メソッドです。

さらに、プライベート プロパティにはゲッター メソッドとセッター メソッドを含めることもできます。

クラス カウンタ {
  #x値 = 0;

  コンストラクター() {
    console.log(this.#x);
  }

  get #x() { return this.#xValue;
  set #x() {
    this.#xValue = 値;
  }
}

上記のコードでは、#x はプライベート プロパティであり、その読み書きは別のプライベート プロパティである #xValueget #x()set #x() で操作することで完了します。

プライベート プロパティは「this」からの参照に限定されず、インスタンスはクラス内にある限りプライベート プロパティを参照することもできます。

クラス Foo {
  #プライベート値 = 42;
  静的 getPrivateValue(foo) {
    foo.#privateValue を返します。
  }
}

Foo.getPrivateValue(new Foo()); // 42

上記のコードにより、プライベート プロパティをインスタンス foo から参照できるようになります。

プライベート プロパティやプライベート メソッドの前に static キーワードを追加して、これが静的なプライベート プロパティまたはプライベート メソッドであることを示すこともできます。

クラス FakeMath {
  静的PI = 22 / 7;
  静的 #totallyRandomNumber = 4;

  静的 #computeRandomNumber() {
    FakeMath.#totallyRandomNumber を返します。
  }

  静的ランダム() {
    console.log('あなたは乱数が好きだと聞きました…')
    FakeMath.#computeRandomNumber() を返します。
  }
}

FakeMath.PI // 3.142857142857143
FakeMath.random()
// 乱数が好きだと聞いたのですが…
// 4
FakeMath.#totallyRandomNumber // エラーレポート
FakeMath.#computeRandomNumber() // エラーレポート

上記のコードでは、#totallyRandomNumber はプライベート プロパティ、#computeRandomNumber() はプライベート メソッドで、FakeMath クラス内でのみ呼び出すことができ、外部から呼び出すとエラーが報告されます。

演算子内の ###

前述したように、クラスの存在しないプライベート プロパティに直接アクセスするとエラーが報告されますが、存在しないパブリック プロパティにアクセスしてもエラーは報告されません。この機能を使用して、オブジェクトがクラスのインスタンスであるかどうかを判断できます。

クラスC {
  #ブランド;

  静的 isC(obj) {
    試す {
      オブジェクト#ブランド;
      true を返します。
    } キャッチ {
      false を返します。
    }
  }
}

上記の例では、クラス「C」の静的メソッド「isC()」を使用して、オブジェクトが「C」のインスタンスであるかどうかを判断します。使用するメソッドは、オブジェクトのプライベート属性 #brand にアクセスすることです。エラーが報告されない場合は「true」が返されます。エラーが報告された場合は、オブジェクトが現在のクラスのインスタンスではないことを意味するため、「catch」部分は「false」を返します。

したがって、try...catch 構造を使用して、プライベート プロパティが存在するかどうかを判断できます。ただし、この書き方は非常に面倒でコードの可読性が非常に悪いため、ES2022 では、次のように in 演算子が改善されました。プライベート属性を決定するために使用することもできます。

クラスC {
  #ブランド;

  静的 isC(obj) {
    if (obj 内の #brand) {
      // プライベート プロパティ #brand が存在します
      true を返します。
    } それ以外 {
      // プライベート プロパティ #foo が存在しません
      false を返します。
    }
  }
}

上の例では、「in」演算子は、オブジェクトがプライベート属性「#brand」を持つかどうかを判断します。エラーは報告されませんが、ブール値が返されます。

この「in」の使用法は、「this」と一緒に使用することもできます。

クラスA {
  #foo = 0;
  m() {
    console.log(この中の#foo); // true
  }
}

private プロパティを判定する場合、in はクラス内でのみ使用できることに注意してください。さらに、判定の対象となるプライベート属性を最初に宣言する必要があります。宣言しないとエラーが報告されます。

クラスA {
  m() {
    console.log(#foo in this); // エラーレポート
  }
}

上記の例ではプライベート属性 #foo が宣言されておらず、そのまま in 演算子の判定に使用されているためエラーとなります。

静的ブロック

静的プロパティに関する問題の 1 つは、静的プロパティに初期化ロジックがある場合、このロジックがクラスの外部または constructor() メソッドの内部に記述されることです。

クラスC {
  静的 x = 234;
  静的 y;
  静的z;
}

試す {
  const obj = doSomethingWith(C.x);
  C.y = オブジェクトy
  C.z = obj.z;
} キャッチ {
  C.y = ...;
  C.z = ...;
}

上記の例では、静的プロパティ yz の値は静的プロパティ x の演算結果に依存します。この初期化ロジックはクラスの外部 (try...catch) に記述されます。上記の例ではコード ブロック)。別の方法は、クラスの constructor() メソッドに記述することです。どちらの方法も理想的ではありません。前者はクラスの内部ロジックを外部に書き込みますが、後者は新しいインスタンスが作成されるたびにそれを実行します。

この問題を解決するために、ES2022 では 静的ブロック (静的ブロック) が導入されました。これにより、クラスが生成され、一度だけ実行されます。主な機能は静的プロパティを初期化することです。今後、クラスの新しいインスタンスを作成するときに、このブロックは実行されなくなります。

クラスC {
  静的 x = ...;
  静的 y;
  静的z;

  静的 {
    試す {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    キャッチ {
      this.y = ...;
      this.z = ...;
    }
  }
}

上記のコードでは、クラス内に静的コード ブロックがあり、これが静的ブロックです。その利点は、静的属性 y および z の初期化ロジックがクラス内に記述され、一度だけ実行されることです。

各クラスは複数の静的ブロックを持つことができ、各静的ブロック内でアクセスできるのは、以前に宣言された静的プロパティのみです。さらに、静的ブロック内に「return」ステートメントを含めることはできません。

クラス名または「this」を静的ブロック内で使用して、現在のクラスを参照できます。

クラスC {
  静的 x = 1;
  静的 {
    this.x; // 1
    // または
    C.x; // 1
  }
}

上記の例では、「this.x」と「C.x」の両方が静的プロパティ「x」を取得できます。

静的プロパティの初期化に加えて、静的ブロックにはプライベート プロパティをクラスの外部コードと共有する役割もあります。

getX をさせます。

エクスポートクラスC {
  #x = 1;
  静的 {
    getX = obj => obj.#x;
  }
}

console.log(getX(new C())); // 1

上の例では、#x はクラスのプライベート プロパティです。クラス外の getX() メソッドがこのプロパティを取得したい場合は、クラスの constructor() メソッド内に記述されていました。この場合、新しい Each インスタンスが作成されるたびに getX() メソッドが定義されます。静的ブロック内に記述できるようになり、クラスの生成時に 1 回だけ定義されるようになりました。

授業に関する注意事項

ストリクトモード

内部的には、クラスとモジュールはデフォルトで strict モードになっているため、実行モードを指定するために use strict を使用する必要はありません。コードがクラスまたはモジュールに記述されている限り、厳密モードのみが使用可能です。今後のすべてのコードが実際にはモジュールで実行されることを考慮すると、ES6 では実際に言語全体が厳密モードにアップグレードされます。

プロモーションはありません

ES5 とはまったく異なり、クラスの変数巻き上げ (ホイスト) がありません。

new Foo(); // 参照エラー
クラスFoo{}

上記のコードでは、Foo クラスが最初に使用され、後で定義されます。これは、ES6 がクラス宣言をコードの先頭に昇格させないため、エラーが発生します。この規定の理由は、後述する継承に関連しており、サブクラスは必ず親クラスの後に定義する必要があります。

{
  Foo = クラス {}; にします。
  クラス Bar extends Foo {
  }
}

BarFoo を継承するとき、Foo はすでに定義されているため、上記のコードはエラーを報告しません。ただし、class の昇格がある場合、上記のコードはエラーを報告します。これは、class がコードの先頭に昇格されますが、Foo を定義する行が昇格されず、Bar が継承されるためです。 FooFoo はまだ定義されていません。

名前属性

本質的に、ES6 クラスは ES5 コンストラクターの単なるラッパーであるため、関数の多くの機能は、name 属性を含め、Class によって継承されます。

クラスポイント {}
Point.name // "ポイント"

name 属性は常に、class キーワードの直後にクラス名を返します。

ジェネレーターメソッド

メソッドの前にアスタリスク (*) が付いている場合は、そのメソッドがジェネレーター関数であることを意味します。

クラス Foo {
  コンストラクター(...args) {
    this.args = args;
  }
  * [Symbol.iterator]() {
    for (this.args の arg を指定) {
      引数を生成します。
    }
  }
}

for (let x of new Foo('hello', 'world')) {
  コンソール.ログ(x);
}
// こんにちは
// 世界

上記のコードでは、「Foo」クラスの「Symbol.iterator」メソッドの前にアスタリスクがあり、このメソッドがジェネレータ関数であることを示しています。 Symbol.iterator メソッドは、Foo クラスのデフォルトのイテレータを返し、for...of ループは自動的にこのイテレータを呼び出します。

これを指して

クラスメソッドに「this」が含まれている場合、デフォルトでクラスのインスタンスを指します。ただし、この方法だけを使用するとエラーが発生する可能性があるので注意が必要です。

クラスロガー{
  printName(name = 'そこ') {
    this.print(`こんにちは ${name}`);
  }

  print(テキスト) {
    コンソール.ログ(テキスト);
  }
}

const logger = new Logger();
const { printName } = ロガー;
printName(); // TypeError: 未定義のプロパティ 'print' を読み取れません

上記のコードでは、printName メソッドの this はデフォルトで Logger クラスのインスタンスを指します。ただし、このメソッドが抽出されて単独で使用される場合、this はメソッドが実行される環境を指します (クラスは内部的に厳密モードであるため、実際には unknown を指します)。その結果、print は実行されません。メソッドが見つかり、エラーが報告されます。

より簡単な解決策は、コンストラクターで this をバインドして、print メソッドが見つからないようにすることです。

クラスロガー{
  コンストラクター() {
    this.printName = this.printName.bind(this);
  }

  // ...
}

もう 1 つの解決策は、アロー関数を使用することです。

クラスオブジェクト{
  コンストラクター() {
    this.getThis = () => this;
  }
}

const myObj = 新しい Obj();
myObj.getThis() === myObj // true

アロー関数内の「this」は、常にそれが定義されているオブジェクトを指します。上記のコードでは、アロー関数はコンストラクター内に配置されており、その定義はコンストラクターが実行されるときに有効になります。このとき、アロー関数が配置されている実行環境はインスタンス オブジェクトである必要があるため、this は常にインスタンス オブジェクトを指します。

別の解決策は、メソッドを取得するときに自動的に this をバインドする Proxy を使用することです。

関数利己的 (ターゲット) {
  const キャッシュ = 新しい WeakMap();
  const ハンドラ = {
    get (ターゲット, キー) {
      const 値 = Reflect.get(ターゲット, キー);
      if (値の型 !== '関数') {
        戻り値;
      }
      if (!cache.has(value)) {
        キャッシュ.セット(値, 値.バインド(ターゲット));
      }
      戻り値cache.get(値);
    }
  };
  const proxy = 新しいプロキシ(ターゲット, ハンドラー);
  プロキシを返す。
}

const logger = 利己的(new Logger());

new.target プロパティ

newはコンストラクタからインスタンスオブジェクトを生成するコマンドです。 ES6 では、「new」コマンドに「new.target」属性が導入されています。この属性は通常、コンストラクターで使用され、「new」コマンドが動作するコンストラクターを返します。コンストラクターが「new」コマンドまたは「Reflect.construct()」を介して呼び出されない場合、「new.target」は「unknown」を返すため、このプロパティを使用してコンストラクターがどのように呼び出されたかを判断できます。

関数 人(名前) {
  if (new.target !== 未定義) {
    this.name = 名前;
  } それ以外 {
    throw new Error('インスタンスを生成するには new コマンドを使用する必要があります');
  }
}

// 別の書き方
関数 人(名前) {
  if (new.target === 人) {
    this.name = 名前;
  } それ以外 {
    throw new Error('インスタンスを生成するには new コマンドを使用する必要があります');
  }
}

var person = new Person('Zhang San'); // 正しい
var notAperson = Person.call(person, 'Zhang San') // エラーレポート

上記のコードは、コンストラクターが「new」コマンドを通じてのみ呼び出せることを保証します。

クラスは内部的に「new.target」を呼び出して現在のクラスを返します。

クラス長方形{
  コンストラクター(長さ, 幅) {
    console.log(new.target === Rectangle);
    this.length = 長さ;
    this.width = 幅;
  }
}

var obj = new Rectangle(3, 4); // true を出力します。

サブクラスが親クラスを継承する場合、new.target はサブクラスを返すことに注意してください。

クラス長方形{
  コンストラクター(長さ, 幅) {
    console.log(new.target === Rectangle);
    // ...
  }
}

クラス Square extends Rectangle {
  コンストラクター(長さ, 幅) {
    super(長さ, 幅);
  }
}

var obj = new Square(3) // false を出力します。

上記のコードでは、new.target はサブクラスを返します。

この機能を利用すると、独立して使用できず、使用する前に継承する必要があるクラスを作成できます。

クラスシェイプ {
  コンストラクター() {
    if (new.target === Shape) {
      throw new Error('このクラスはインスタンス化できません');
    }
  }
}

クラス Rectangle extends Shape {
  コンストラクター(長さ, 幅) {
    素晴らしい();
    // ...
  }
}

var x = new Shape(); // エラー
var y = new Rectangle(3, 4); // 正しい

上記のコードでは、「Shape」クラスはインスタンス化できず、継承のみに使用できます。

関数の外で「new.target」を使用するとエラーが報告されることに注意してください。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0