keys と values と entries

このレッスンで分かること

  • Object.keys Object.values Object.entries はオブジェクトを配列に変えて、第2章のメソッドを使えるようにします
  • Object.entries が返すのは [キー, 値] の配列なので、分割代入と相性がよいです
  • Object.fromEntries で配列からオブジェクトへ戻せます

keys と values と entries とは

オブジェクトを配列として扱えるようにする 3 つの関数です。配列に変えてしまえば mapfilterreduce がそのまま使えます。

オブジェクトには map が無い

第2章では作品の配列を mapfilterreduce でつないできました。ところが、タイトルごとの表示回数のように「キーそのものがデータ」である形は、配列ではなくオブジェクトで持つほうが自然です。

const views = { "プロフィールサイト": 120, "自己紹介カード": 48, "学習ログ": 0 }; views.map((value) => value * 2); // TypeError views.map is not a function

オブジェクトには mapfilter もありません。並び順という概念を持たないデータ構造なので、配列のメソッドが生えていないのは筋が通っています。そこで、いったん配列に変換します。

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.fromEntriesentries の逆向きです。

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 は正しく組み立てられません。

fromEntriesMap からオブジェクトを作るときにも使えます。逆に 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 つ、keysentries が拾うのはそのオブジェクト自身の列挙可能なキーだけです。クラスのメソッドや継承したものは含まれません。第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 は合計や最大値のように、キーを完全に捨ててよいときだけ使います。

要件

  1. 関数名は summarizeViewCounts。引数はタイトルをキー、表示回数を値に持つオブジェクト 1 つ
  2. titles は Object.keys で取り出したタイトルの配列にする
  3. total は Object.values の合計にする。キーが 1 つも無いときは 0
  4. active は表示回数が 1 以上のものだけを残したオブジェクトにする。Object.entries と Object.fromEntries を使う
  5. 戻り値は { 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}

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.js
main.js
学習モード

メモ

keys と values と entries

⌘S で保存