2022年5月3日

静的プロパティとメソッド

クラス全体にメソッドを割り当てることもできます。そのようなメソッドは静的と呼ばれます。

クラス宣言では、このようなstaticキーワードが前に追加されます

class User {
  static staticMethod() {
    alert(this === User);
  }
}

User.staticMethod(); // true

これは実際に、直接プロパティとして割り当てるのと同じことです

class User { }

User.staticMethod = function() {
  alert(this === User);
};

User.staticMethod(); // true

User.staticMethod()呼び出しにおけるthisの値は、クラスコンストラクタUserそのものです(「ドットより前のオブジェクト」ルール)。

通常、静的メソッドはクラス全体に属するが、その特定のオブジェクトには属さない関数を実装するために使用されます。

例えば、Articleオブジェクトがあり、それらを比較する関数が求められているとします。

自然な解法はArticle.compare静的メソッドを追加することです

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static compare(articleA, articleB) {
    return articleA.date - articleB.date;
  }
}

// usage
let articles = [
  new Article("HTML", new Date(2019, 1, 1)),
  new Article("CSS", new Date(2019, 0, 1)),
  new Article("JavaScript", new Date(2019, 11, 1))
];

articles.sort(Article.compare);

alert( articles[0].title ); // CSS

ここでArticle.compareメソッドは、記事の「上」にあり、それらの比較に使用されます。これら記事のメソッドではなく、むしろクラス全体のメソッドです。

もう1つの例としては、いわゆる「ファクトリ」メソッドがあります。

記事を作成するための方法が複数必要だとしましょう

  1. 指定されたパラメータ(titledateなど)による作成
  2. 今日の日付で空の記事を作成する
  3. …あるいは他の何らかの方法

最初の方法はコンストラクタによって実装できます。2番目のメソッドについては、クラスの静的メソッドを作成できます。

ここではArticle.createTodays()です

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static createTodays() {
    // remember, this = Article
    return new this("Today's digest", new Date());
  }
}

let article = Article.createTodays();

alert( article.title ); // Today's digest

今日の日刊を作成する必要があるたびに、Article.createTodays()を呼び出すことができます。繰り返しますが、これは記事のメソッドではなく、クラス全体のメソッドです。

静的メソッドは、データベース関連のクラスでも次のように使用されます。データベースからエントリを検索/保存/削除

// assuming Article is a special class for managing articles
// static method to remove the article by id:
Article.remove({id: 12345});
静的メソッドは個々のオブジェクトに対して使用できません

静的メソッドは個々のオブジェクトではなく、クラスで呼び出すことができます。

たとえば、このようなコードは動作しません。

// ...
article.createTodays(); /// Error: article.createTodays is not a function

静的プロパティ

最近追加された機能
これは、言語への最近追加された機能です。最新の Chrome でサンプルが動作します。

静的プロパティも可能です。通常のクラスプロパティのように見えますが、static が前に付きます。

class Article {
  static publisher = "Ilya Kantor";
}

alert( Article.publisher ); // Ilya Kantor

これは、Article に直接代入するのと同じです。

Article.publisher = "Ilya Kantor";

静的プロパティとメソッドの継承

静的プロパティとメソッドは継承できます。

たとえば、次のコードでは、Animal.compareAnimal.planet は継承され、Rabbit.compareRabbit.planet としてアクセスできます。

class Animal {
  static planet = "Earth";

  constructor(name, speed) {
    this.speed = speed;
    this.name = name;
  }

  run(speed = 0) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  static compare(animalA, animalB) {
    return animalA.speed - animalB.speed;
  }

}

// Inherit from Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbits = [
  new Rabbit("White Rabbit", 10),
  new Rabbit("Black Rabbit", 5)
];

rabbits.sort(Rabbit.compare);

rabbits[0].run(); // Black Rabbit runs with speed 5.

alert(Rabbit.planet); // Earth

これで Rabbit.compare を呼び出すと、継承された Animal.compare が呼び出されます。

これはどのように動作するでしょうか?ここでもプロトタイプを使用します。すでに想像しているかもしれませんが、extendsRabbitAnimal への [[Prototype]] 参照を与えます。

したがって、Rabbit extends Animal は 2 つの [[Prototype]] 参照を作成します。

  1. Rabbit 関数は、Animal 関数からプロトタイプ的に継承します。
  2. Rabbit.prototype は、Animal.prototype からプロトタイプ的に継承します。

結果として、継承は通常のメソッドと静的メソッドの両方で機能します。

ここでコードでこれを確認します。

class Animal {}
class Rabbit extends Animal {}

// for statics
alert(Rabbit.__proto__ === Animal); // true

// for regular methods
alert(Rabbit.prototype.__proto__ === Animal.prototype); // true

まとめ

静的メソッドは、「全体として」クラスに属する機能に使用されます。具体的なクラスインスタンスとは関係ありません。

たとえば、比較用のメソッド Article.compare(article1, article2) や、ファクトリーメソッド Article.createTodays()

クラス宣言で static という単語でラベル付けされます。

静的プロパティは、クラスレベルのデータを格納したいとき、インスタンスにもバインドされていないときに使用されます。

構文は次のとおりです。

class MyClass {
  static property = ...;

  static method() {
    ...
  }
}

技術的には、静的宣言はクラス自体に代入するのと同じです。

MyClass.property = ...
MyClass.method = ...

静的プロパティとメソッドは継承できます。

class B extends A の場合、クラス B 自体のプロトタイプは A を指します。B.[[Prototype]] = A。したがって、フィールドが B で見つからない場合、検索は A で続行されます。

タスク

重要度: 3

ご存じのとおり、通常のオブジェクトはすべて Object.prototype から継承され、hasOwnProperty など、汎用オブジェクトメソッドにアクセスできます。

たとえば、

class Rabbit {
  constructor(name) {
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

// hasOwnProperty method is from Object.prototype
alert( rabbit.hasOwnProperty('name') ); // true

しかし、"class Rabbit extends Object" のように明示的に記述した場合、単純な "class Rabbit" とは異なる結果でしょうか?

違いは何でしょうか?

このようなコードの例を次に示します(動作しません。なぜでしょうか?修正してください)

class Rabbit extends Object {
  constructor(name) {
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

alert( rabbit.hasOwnProperty('name') ); // Error

まず、後者のコードが動作しない理由を確認します。

実行しようとすると理由が明らかになります。継承クラスのコンストラクタは super() を呼び出す必要があります。そうしないと、「this」が「定義」されません。

これが修正方法です。

class Rabbit extends Object {
  constructor(name) {
    super(); // need to call the parent constructor when inheriting
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

alert( rabbit.hasOwnProperty('name') ); // true

しかし、それだけではありません。

この修正を加えても、"class Rabbit extends Object"class Rabbit の間に依然として重要な違いがあります。

ご存じのとおり、「extends」構文は 2 つのプロトタイプを設定します。

  1. コンストラクタ関数の「プロトタイプ」(メソッド用)
  2. コンストラクタ関数自体(静的メソッド用)

class Rabbit extends Object の場合は、次を意味します。

class Rabbit extends Object {}

alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) true

つまり、RabbitObject の静的メソッドへのアクセスを、次のように Rabbit 経由で提供します。

class Rabbit extends Object {}

// normally we call Object.getOwnPropertyNames
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b

ただし、extends Object がない場合、Rabbit.__proto__Object に設定されません。

デモを次に示します。

class Rabbit {}

alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) false (!)
alert( Rabbit.__proto__ === Function.prototype ); // as any function by default

// error, no such function in Rabbit
alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error

したがって、この場合、RabbitObject の静的メソッドへのアクセスを提供しません。

ちなみに、Function.prototype には、「汎用」関数メソッド(callbind など)もあります。これらは、組み込みの Object コンストラクタの場合、最終的にどちらのケースでも利用できます。これは、Object.__proto__ === Function.prototype のためです。

図は次のとおりです。

つまり、簡単に言うと、2 つの違いがあります。

class Rabbit class Rabbit extends Object
コンストラクタで super() を呼び出す必要があります。
Rabbit.__proto__ === Function.prototype Rabbit.__proto__ === Object
チュートリアルのマップ

コメント

コメントする前にこれをお読みください...
  • 改善点の提案がある場合は、コメントするのではなく、GitHub の issue の送信またはプルリクエストを送信してください。
  • 記事の一部が理解できない場合は、詳しく説明してください。
  • コードを数行挿入するには <code> タグを使用し、数行の場合は <pre> タグで囲み、10 行を超える場合はサンドボックス(plnkrjsbincodepen...)を使用します。