Q1static count = 0; の Parcel から作った first で first.count を読むと?
インスタンスメンバーと static — class が持つ値と処理
インスタンスではなく class そのものに値や処理を持たせる static を扱います。作った件数を数える static フィールド、JSON から作るファクトリメソッド、共通の定数、インスタンスメンバーとの使い分けを学べます。
配送伝票に 1 から順に番号を振るには、作った枚数をどこかに覚えておく必要があります。宛名は 1 枚ごとに違いますが、作った枚数や重さの上限は全伝票で 1 つあれば足ります。
この記事では、インスタンスではなく class そのものに値やメソッドを持たせる static を扱います。
伝票の枚数を数える — static フィールド
配送伝票に、作った順の番号を振るとします。クラスフィールドに count = 0; と書いて constructor で this.count += 1 と増やしてから番号に使っても、フィールドは new のたびに 0 から入り直すため、どの伝票の番号も 1 になります。
static フィールド(static を付けて class の中に書き、class そのものが 1 つだけ持つプロパティ)は、Parcel.count のように class 名から読み書きします。
これに対し、this.number のようにインスタンスごとに持つプロパティと、static を付けずに書くメソッドをインスタンスメンバーと呼びます。
class Parcel {
// class に 1 つだけある、これまでに作った伝票の枚数
static count = 0;
constructor(recipient) {
Parcel.count += 1; // class の count を 1 増やす
this.number = Parcel.count; // その時点の枚数を伝票番号にする
this.recipient = recipient;
}
}
// 2 枚作る
const first = new Parcel("青木 様");
const second = new Parcel("石田 様");
console.log(first.number, second.number); // 1 2
console.log(Parcel.count); // 2
console.log(first.count); // undefined
static count— 1 枚目のnewで 1、2 枚目のnewで 2- 読み書きは
Parcel.countと書く
number— 1(作った時点の count)recipient— 「青木 様」countは無い —first.countはundefined
number— 2(作った時点の count)recipient— 「石田 様」
count は class の枠に 1 つだけで、インスタンスの枠にはありません。インスタンスから読むと undefined です。2 回の new は、どちらも class の枠にある同じ count を 1 増やし、その時点の値を自分の number に写しています。後から count が 2 に増えても、first.number は作った時点の 1 のままです。
JSON から伝票を作る — static メソッド
保存しておいた伝票を読み込むと、届くのは宛名と重さを並べた JSON 文字列です。読み込む画面ごとに JSON.parse と new Parcel(...) を書くと、JSON のキー名が変わったときに、その画面をすべて直すことになります。
JSON から伝票を作る時点では、まだインスタンスがありません。static メソッド(static を付けて定義し、インスタンスを作らずに class 名から呼ぶメソッド)なら、Parcel.fromJSON(text) の形で呼べます。fromJSON のように、インスタンスを作って返す static メソッドをファクトリメソッドと呼びます。
class Parcel {
constructor(recipient, weightKg) {
this.recipient = recipient;
this.weightKg = weightKg;
}
label() {
return `${this.recipient}(${this.weightKg} kg)`;
}
// JSON 文字列を受け取り、Parcel のインスタンスを作って返す
static fromJSON(text) {
const data = JSON.parse(text);
return new Parcel(data.recipient, data.weightKg);
}
}
const text = '{"recipient":"青木 様","weightKg":2}';
const parcel = Parcel.fromJSON(text); // class 名から呼ぶ
console.log(parcel.label()); // 青木 様(2 kg)
JSON.parse の段では、プロパティはあってもメソッドはありません。new を通った段で、label() を呼べるインスタンスになります。fromJSON の最後の行は new Parcel(...) なので、constructor の本体も普段どおり動きます。constructor に throw の検査を書いておけば、JSON から読み戻した伝票も、画面で入力した伝票と同じ検査を通ります。
fromJSON はインスタンスから呼べない
インスタンスから parcel.fromJSON(text) と呼ぶと、TypeError: parcel.fromJSON is not a function で止まります。label() と違い、static を付けたメソッドは class 名からしか呼べません。仕組みはプロトタイプの記事で扱います。
全伝票で変えない値を置く — static 定数
重さの上限 30 kg は、伝票を作る前の入力フォームの案内にも、伝票ごとの残り重量の計算にも使います。上限をインスタンスのフィールドに置くと、フォームに案内を出すためだけに、宛名も重さも無い伝票を 1 枚作ることになります。
static 定数(途中で変えない値を持たせた static フィールド)は、static MAX_WEIGHT_KG = 30; のように名前をすべて大文字にし、単語を _ でつなぎます。class の外でもメソッドの中でも、Parcel.MAX_WEIGHT_KG と class 名を付けて読みます。
class Parcel {
// 全伝票で共通の上限は、static で class に 1 つだけ置く
static MAX_WEIGHT_KG = 30;
constructor(weightKg) {
this.weightKg = weightKg;
}
// 上限まであと何 kg 入れられるか
remainingKg() {
return Parcel.MAX_WEIGHT_KG - this.weightKg;
// this.MAX_WEIGHT_KG - this.weightKg と書くと NaN が返る
}
}
// インスタンスを作る前から読める
console.log(`1 個 ${Parcel.MAX_WEIGHT_KG} kg まで`); // 1 個 30 kg まで
const parcel = new Parcel(12);
console.log(parcel.remainingKg()); // 18
右の列が NaN になるのは、メソッドの中の this がインスタンスを指し、static 定数はインスタンスの側に無いためです。undefined - 12 はエラーにならないので、表示された残り重量がおかしくなって初めて気づきます。
大文字の名前でも代入は止まらない
大文字の名前は「変えない値」を示す慣習で、Parcel.MAX_WEIGHT_KG = 50; と代入すると書き換わります。static get MAX_WEIGHT_KG() { return 30; } と getter だけを書けば、代入は TypeError で止まります(getter は次の記事で扱います)。
値の置き場所を決める — static とインスタンス
static を覚えると、宛名のような値も class に置けば Parcel.recipient の 1 か所から読めるように見えます。伝票を 1 枚しか作らない動作確認では、static に置いても this に置いても同じ宛名が表示されるため、違いに気づけません。
置き場所は、値が 1 件ごとに違うかで決めます。this に入れた値は new のたびに作られるインスタンスごとに別々にあり、static の値は何枚作っても class に 1 つだけです。
// 宛名を static に置く
class SharedParcel {
static recipient = "";
constructor(recipient) { SharedParcel.recipient = recipient; }
label() { return `宛名: ${SharedParcel.recipient}`; }
}
// 宛名をインスタンスに置く
class Parcel {
constructor(recipient) { this.recipient = recipient; }
label() { return `宛名: ${this.recipient}`; }
}
const sharedFirst = new SharedParcel("青木 様");
new SharedParcel("石田 様");
console.log(sharedFirst.label()); // 宛名: 石田 様
const first = new Parcel("青木 様");
new Parcel("石田 様");
console.log(first.label()); // 宛名: 青木 様
sharedFirst は自分の宛名を持たず、label() は呼んだ時点の SharedParcel.recipient を読むため、最後に代入された「石田 様」が出ます。下の表は、この記事の伝票で使ったメンバーの書き方と使い方を並べたものです。
| 持たせるもの | 書き方と使い方 | この記事の例 |
|---|---|---|
| 1 件ごとに違う値 | this に入れ、インスタンスから読む | number / recipient / weightKg |
| 1 件ごとの値を読むメソッド | static を付けずに書き、インスタンスから呼ぶ | label() / remainingKg() |
| 全件で 1 つの値 | static を付けて書き、class 名から読む | count / MAX_WEIGHT_KG |
| インスタンスが無くても呼ぶ処理 | static を付けて書き、class 名から呼ぶ | fromJSON(text) |
理解度チェック
まずは1問ずつ答えてみましょう。
Q2インスタンス parcel から static メソッドを parcel.fromJSON(text) と呼ぶと?
Q3宛名を static に置く class で「青木 様」「石田 様」の順に作ると、1 枚目の label() の宛名は?