つくる 作品データを構造化する
このレッスンで分かること
- 集計関数は、返す形を先に決めてから中身を書きます
- 欠けたプロパティと空配列に耐える関数を書けます
1 件でも欠けていると、合計が NaN になる
第7章を通して、プロフィールサイトのデータはタイトルだけの配列から、1 件ごとに複数の情報を持つオブジェクトの配列へ育ちました。総仕上げとして、その配列から件数と合計閲覧数と最新作をまとめて返す関数を書きます。決まりは次のとおりです。
- 件数は配列の長さをそのまま使う
- 閲覧数の合計は、
viewsが無い作品を0として足す - 最新作は
yearが最大の作品のタイトル。同じ年が並んだら、先に出てきたほうを採用する - 空配列のときは、件数も合計も
0、最新作はnull
いちばん多い失敗は 2 つ目です。下書き中の作品にはまだ閲覧数が無いかもしれないのに、そのまま足してしまうケースです。
JavaScript
console.log(0 + undefined); // NaN一度 NaN が混ざると、その後どれだけ足しても NaN のままです。画面には「NaN 回」という意味不明な表示が出ます。第3章の ?? を入り口に置けば防げます。
JavaScript
const entry = { label: "CSS" }; // level が無い
console.log(entry.level ?? 0); // 0 無いときだけ 0 を使う|| ではなく ?? を選ぶのは、本当に 0 だった値をそのまま残したいからです。今回はどちらでも同じ結果になりますが、数値には ?? と決めておくと事故りません。
集めるものは、1 回のループで全部集める
3 つの値を求めるからといって、ループを 3 回まわす必要はありません。足し上げながら、同時に最大値も追いかけられます。最大値を追う変数は、初期値を「必ず負ける値」にしておくのが定石です。
JavaScript
let topLevel = -Infinity;
console.log(3 > topLevel); // true 1 件目で必ず更新される0 を初期値にしても多くの場合は動きますが、-Infinity のほうが「まだ何も見ていない」という意図がはっきりします。件数のように、すでに用意されているものは自分で数えません。
JavaScript
console.log(["HTML", "CSS", "JavaScript"].length); // 3同点のときに、どちらを残すか
同じ年が並んだときにどちらを採用するかは、比較の記号ひとつで決まります。
JavaScript
let top = 2026;
console.log(2026 > top); // false 更新されないので先に見たほうが残る
console.log(2026 >= top); // true 更新されるので後から来たほうに入れ替わる先に出てきたほうを残したいなら > です。ここを >= にすると、同じ年が並ぶケースだけテストが落ちます。
空配列のときの答えも、実装の前に決めておきます。ループが 1 度もまわらないので、初期値がそのまま戻り値になります。
| 初期値の候補 | 呼び出し側での扱い |
|---|---|
null | 値が無いことを意図して表せる |
undefined | キーの書き忘れと区別が付かない |
"" | 空文字と未登録が区別できない |
"なし" | 表示用の文言が集計関数に混ざる |
今回は null を選びます。値が無いことを意図して表すという null 本来の意味に合っていて、呼び出し側も latest ?? "まだ作品がありません" の 1 行で表示を決められるからです。表示の文言を集計関数に持ち込まないのは、覚えておく価値のある習慣です。
よくある間違い
- 戻り値のキー名を間違える — キー名は関数の外向きの約束です。つづりが違っても
undefinedが返るだけでエラーにならないので、気付くのが遅れます - 欠けたプロパティを守らずに足す —
NaNはどんな比較でもfalseを返すため、if (total > 0)のような判定もすり抜けます。入り口で??を使うのがいちばん確実です
要件
- 関数名は
summarizeWorks、引数は作品オブジェクトの配列worksの 1 つ - 戻り値は
counttotalViewslatestの 3 つのキーを持つオブジェクト viewsが無い作品は0として合計することlatestはyearが最大の作品のtitle。同じ年なら先に出てきたほうを採用すること- 空配列のときは
{ count: 0, totalViews: 0, latest: null }を返すこと
入出力例
summarizeWorks([{"published":true,"title":"プロフィールサイト","views":120,"year":2024},{"published":true,"title":"自己紹介カード","views":80,"year":2026},{"published":false,"title":"スキル一覧の表","views":40,"year":2025}]) → {"count":3,"latest":"自己紹介カード","totalViews":240}
summarizeWorks([{"published":true,"title":"プロフィールサイト","views":120,"year":2026}]) → {"count":1,"latest":"プロフィールサイト","totalViews":120}
summarizeWorks([]) → {"count":0,"latest":null,"totalViews":0}
summarizeWorks([{"title":"自己紹介カード","year":2026},{"title":"スキル一覧の表","views":40,"year":2025}]) → {"count":2,"latest":"自己紹介カード","totalViews":40}
summarizeWorks([{"title":"プロフィールサイト","views":10,"year":2026},{"title":"自己紹介カード","views":5,"year":2026}]) → {"count":2,"latest":"プロフィールサイト","totalViews":15}