keys と values と entries
このレッスンで分かること
Object.keysObject.valuesObject.entriesはオブジェクトを配列に変えて、第2章のメソッドを使えるようにしますObject.entriesが返すのは[キー, 値]の配列なので、分割代入と相性がよいですObject.fromEntriesで配列からオブジェクトへ戻せます
keys と values と entries とは
オブジェクトを配列として扱えるようにする 3 つの関数です。配列に変えてしまえば
mapやfilterやreduceがそのまま使えます。
オブジェクトには map が無い
第2章では作品の配列を map と filter と reduce でつないできました。ところが、タイトルごとの表示回数のように「キーそのものがデータ」である形は、配列ではなくオブジェクトで持つほうが自然です。
const views = {
"プロフィールサイト": 120,
"自己紹介カード": 48,
"学習ログ": 0
};
views.map((value) => value * 2); // TypeError views.map is not a functionオブジェクトには map も filter もありません。並び順という概念を持たないデータ構造なので、配列のメソッドが生えていないのは筋が通っています。そこで、いったん配列に変換します。
console.log(Object.keys(views)); // ["プロフィールサイト", "自己紹介カード", "学習ログ"]
console.log(Object.values(views)); // [120, 48, 0]
console.log(Object.entries(views)); // [["プロフィールサイト", 120], ["自己紹介カード", 48], ["学習ログ", 0]]3 つとも Object に生えた関数で、引数にオブジェクトを渡します。views.keys() のようにドットで呼ぶのではない点に気を付けてください。
| 関数 | 返るもの | 使いどころ |
|---|---|---|
Object.keys(obj) | キーの配列 | 件数を数える、キーの一覧を出す |
Object.values(obj) | 値の配列 | 合計や最大値を求める |
Object.entries(obj) | [キー, 値] の配列 | キーと値の両方が要るとき |
Object.fromEntries(arr) | オブジェクト | 加工した結果をオブジェクトに戻す |
entries と分割代入を組み合わせる
Object.entries が返す 1 件は ["プロフィールサイト", 120] という 2 要素の配列です。0 番目がキー、1 番目が値と位置が決まっているので、前のレッスンの配列の分割代入がそのまま効きます。
const entries = Object.entries(views);
const labels = entries.map(([title, count]) => title + " " + count + " 回");
console.log(labels);
// ["プロフィールサイト 120 回", "自己紹介カード 48 回", "学習ログ 0 回"]([title, count]) => ... の内側の [] が分割代入です。entry[0] entry[1] と数字で書くより、何が入っているのかがはっきりします。値だけ使うなら、キーの位置をカンマで飛ばせます。
const active = entries.filter(([, count]) => count > 0);
console.log(active.length); // 2先頭のカンマだけが置かれた [, count] は見慣れないうちは戸惑いますが、「1 番目は要らない」という宣言です。
Object.entriesは第2章で書いたgroupByの結果を扱うときにも使います。groupByはカテゴリ名をキーにしたオブジェクトを返すので、そのままでは並べ替えられません。entriesで配列にしてからsortする、という流れは覚えておく価値があります。
fromEntries でオブジェクトに戻す
配列にして加工したら、オブジェクトの形に戻したい場面があります。Object.fromEntries は entries の逆向きです。
const activeViews = Object.fromEntries(
Object.entries(views).filter(([, count]) => count > 0)
);
console.log(activeViews);
// { "プロフィールサイト": 120, "自己紹介カード": 48 }entries で開き、配列のメソッドで加工し、fromEntries で閉じる。この 3 段が、オブジェクトを絞り込んだり値を書き換えたりする定石です。値を 2 倍にするなら真ん中を map に変えるだけです。
const doubled = Object.fromEntries(
Object.entries(views).map(([title, count]) => [title, count * 2])
);map が返しているのが [キー, 値] の 2 要素配列である点に注意してください。オブジェクトを返すと fromEntries は正しく組み立てられません。
fromEntriesはMapからオブジェクトを作るときにも使えます。逆にnew Map(Object.entries(obj))でオブジェクトからMapを作れます。キーに文字列以外を使いたくなったらMapの出番ですが、このコースで扱うデータは全て文字列のキーで足ります。
順番と、拾われないキー
オブジェクトのキーには「書いた順」が概ね保たれますが、例外が 1 つあります。数字に見えるキーは昇順で先に並びます。
const scores = { "10": "a", "2": "b", "x": "c" };
console.log(Object.keys(scores)); // ["2", "10", "x"]作品タイトルをキーにしている限り遭遇しませんが、id をキーにしたオブジェクトを作ると順番が変わって見えることがあります。順番が意味を持つデータは配列で持つ、という原則に戻ってください。
もう 1 つ、keys や entries が拾うのはそのオブジェクト自身の列挙可能なキーだけです。クラスのメソッドや継承したものは含まれません。第5章でクラスを扱ったあとに Object.keys(instance) を書くと、フィールドだけが並んでメソッドが出てこないのはこのためです。
よくある間違い
- ドットで呼ぼうとする
views.keys(); // TypeError views.keys is not a function
Object.keys(views); // 正しいkeys はオブジェクトのメソッドではなく Object に生えた関数です。配列の arr.map() とは呼びかたが違います。
- map の中でオブジェクトを返してしまう
Object.fromEntries(
Object.entries(views).map(([title, count]) => ({ title: count }))
);
// {} に近い形になり、意図した結果にならないfromEntries が受け取れるのは [キー, 値] の 2 要素配列の並びです。[title, count] の形で返してください。
- 値だけ取ってキーを失う
const total = Object.values(views).reduce((sum, count) => sum + count, 0);
const top = Object.values(views).filter((count) => count > 100);
// top には 120 しか残らず、どの作品か分からないキーが必要になる可能性があるなら、最初から entries で開きます。values は合計や最大値のように、キーを完全に捨ててよいときだけ使います。
要件
- 関数名は summarizeViewCounts。引数はタイトルをキー、表示回数を値に持つオブジェクト 1 つ
- titles は Object.keys で取り出したタイトルの配列にする
- total は Object.values の合計にする。キーが 1 つも無いときは 0
- active は表示回数が 1 以上のものだけを残したオブジェクトにする。Object.entries と Object.fromEntries を使う
- 戻り値は { titles, total, active } のオブジェクト
入出力例
summarizeViewCounts({"プロフィールサイト":120,"学習ログ":0,"自己紹介カード":48}) → {"active":{"プロフィールサイト":120,"自己紹介カード":48},"titles":["プロフィールサイト","自己紹介カード","学習ログ"],"total":168}
summarizeViewCounts({}) → {"active":{},"titles":[],"total":0}
summarizeViewCounts({"スキル一覧の表":0}) → {"active":{},"titles":["スキル一覧の表"],"total":0}
summarizeViewCounts({"作品ギャラリー":7}) → {"active":{"作品ギャラリー":7},"titles":["作品ギャラリー"],"total":7}