つくる 作品コレクションをクラスにする
このレッスンで分かること
- 作品 1 件を表すクラスと、その集まりを表すクラスに役割を分けられます
- 配列そのものを
#で隠すと、外からpushやspliceで壊されなくなります
作品の配列が、誰からでも触れる場所にある
入門で書いた script.js では、作品の配列がグローバルに 1 本置いてありました。描画も絞り込みも集計も、その同じ配列を直接見ています。この形は、書き換える処理が増えた瞬間に崩れます。
JavaScript
works.push({ title: "学習ログ" }); // 年も表示回数も無いまま入った
works.sort((a, b) => b.views - a.views); // 元の順番が消えたどちらもエラーになりません。困るのはずっとあとで、一覧の並びが勝手に変わったり、集計だけ undefined になったりしてからです。原因を探すには、配列を触っている場所を全部読むことになります。
この回では、そこに蓋をします。作品 1 件のことだけを知るクラスと、複数あることだけを知るクラスに分け、配列は後者の中に隠します。役割は次のように分かれます。
- 1 件のクラスは、渡された記録を持ち、表示用の 1 行を組み立てる
- 集まりのクラスは、足す、数える、公開中だけ選ぶ、合計する、上位を並べる
- 画面に渡す関数は、集まりのクラスに聞くだけで、配列そのものは受け取らない
入口を 1 本だけ開ける
隠すのに使うのは # です。この章でプロパティを隠すのに使ったものが、そのまま配列にも使えます。
JavaScript
class Deck {
#cards = [];
add(card) {
this.#cards.push(card);
return this;
}
get size() {
return this.#cards.length;
}
}
const deck = new Deck().add("赤").add("青");
console.log(deck.size); // 2#cards は外から見えないので、足す道は add だけになります。必ず add の中を通るので、「入っているものはこの形だ」と言い切れます。add が this を返しているのでつなげて書けます。
受け取る値が増えてきたら、constructor({ id, title }) のようにオブジェクトを分割代入で受けます。呼ぶ側は記録をそのまま渡せて、引数の順番を覚えずに済みます。
並び替えで、元の順番を壊さない
上位を取り出すには sort を使いますが、sort は元の配列を並び替えます。隠した配列に直接かけると、隠した意味がなくなります。
JavaScript
const source = [3, 1, 2];
const sorted = source.filter((n) => n > 0).sort((a, b) => b - a);
console.log(sorted); // [3, 2, 1]
console.log(source); // [3, 1, 2] 元は無事filter は毎回新しい配列を作って返します。だからその戻り値に sort をつないでも、元は無事です。先に絞り込んでから並び替える、という順番がそのまま安全策になります。上位 N 件はそこから slice(0, N) で切り出します。
外に返すのは、クラスではなく素のオブジェクトにしておくと後が楽です。画面を描く側がクラスの中身を知らずに済むので、あとで作りを変えても描画のコードを直さずに済みます。
よくある間違い
- 隠した配列をそのまま返す —
get cards() { return this.#cards; }のような getter を足すと、返した配列は同じ入れ物なので、外からpushもspliceも通ります。隠した意味がありません。渡すならコピーを返します - 合計と件数で、数える対象がずれる — 公開中だけを並べているのに合計は全件から取っている、という食い違いは、画面を見ても気づけません。絞り込んだ結果をひとつのメソッドから取り出して、合計も件数もそれを使います
- 件数を渡さずに上位を取り出す —
slice(0, undefined)は全件を返します。0 件のつもりで呼ぶと全部出てくるので、呼ぶ側で件数を決めて必ず渡します
要件
- class Work は constructor で { id, title, year, views, published } を分割代入で受け取る。views は #views に入れ、get views() と get line() を用意する。line は
タイトル (年) N 回の形 - class WorkCollection は #works を空の配列で持つ。add(record) は Work を作って足し this を返す。get size() は件数を返す
- published() は published が true の Work だけの配列を返す。totalViews() はその views の合計。topTitles(limit) はその中を views の多い順に並べた先頭 limit 件のタイトルの配列
- static fromRecords(records) は WorkCollection を作って records を全部 add してから返す
- function buildCollectionView(records, limit) を定義し、{ size, publishedCount, totalViews, topTitles, lines } を返す。lines は published() の line を元の順番のまま並べた配列