オブジェクトは「名前付きの値」の集まり
配列が値を位置で並べるのに対して、JavaScript の object は値に名前をつけて束ねるものです。ユーザーなら name と age、リクエストなら method と URL といったように、ラベル付きのパーツで構成されるものを表現したいときは、迷わずオブジェクトの出番です。
一番手軽な作り方は、オブジェクトリテラルを使う方法です。
エントリはすべて key: value の形になっています。キーは内部的には文字列で(name のように有効な識別子であればクォートは省略可)、値は JavaScript が表現できるものなら何でも入ります。数値・文字列・真偽値・配列・関数、もちろん別のオブジェクトもOKです。
最後のエントリの後ろにカンマ(トレーリングカンマ)が付いていても問題ありません。むしろそのまま残しておくチームが多いですね。後からプロパティを追加したときに diff がきれいに収まるからです。
プロパティの読み書き
プロパティへのアクセス方法は2つ。ドット記法とブラケット記法です。
ドット記法のほうがスッキリ書けるので、基本はこちらを使えばOKです。一方、ブラケット記法が活きるのは、キーを変数に入れて動的にアクセスしたいときや、キー名が有効な識別子として使えない場合です。
存在しないプロパティを読み取っても、エラーにはならず undefined が返ってきます。
そうすると、タイポがあっても静かに見逃されてしまいます。ちゃんとエラーで気づきたいなら、自分で明示的にチェックする必要があります。
プロパティの追加と削除
JavaScript のオブジェクトは柔軟で、あとからいくらでもキーを追加できます:
削除するときは delete を使います。
delete は毎日使うような演算子ではありませんが、キーを単に undefined にするのではなく完全に取り除きたいときには、これがぴったりの道具です。user.email = undefined としてもキー自体は残るので、"email" in user は依然として true を返してしまいます。
プロパティの存在を確認する方法
チェックの仕方は3通りあり、それぞれ微妙に意味が異なります。
inはプロトタイプチェーンから継承されたキーも含めて、キーが存在するかどうかをチェックします。Object.hasOwn(obj, key)はオブジェクト自身のキーだけをチェックします。継承されたものを無視したいときはこちらを使いましょう。古いhasOwnPropertyの置き換えとして使えます。obj.key !== undefinedでもだいたいは動きますが、プロパティに明示的にundefinedがセットされている場合は誤判定します。
迷ったら Object.hasOwn を選んでおけば、まず意図どおりに動いてくれます。
メソッド: オブジェクトに紐づく関数
値が関数になっているプロパティのことをメソッドと呼びます。オブジェクトリテラルの中では、次のような短縮構文で定義できます。
メソッド内の this は、そのメソッドが呼び出されたオブジェクト、つまり今回でいう user を指します。これのおかげで、greet は誰の名前を使えばいいか分かるわけですね。
ただし注意点が一つ。アロー関数は自分自身の this を持たないので、オブジェクトの他のプロパティを参照するメソッドには向いていません:
this を扱うときは、ショートハンドのメソッド記法(greet() { ... })を使いましょう。アロー関数はコールバックには便利ですが、オブジェクトのメソッドには向きません。
ネストされたオブジェクト
JavaScript のオブジェクトは、値として別のオブジェクトを持つこともできます。いくらでも入れ子にできます。
ネストされたプロパティを読み取るには、user.address.city のようにチェーンでつないでいきます。ただし注意点があって、途中のどこかが null や undefined になっていると TypeError が発生します。
console.log(user.profile.city);
// TypeError: Cannot read properties of undefined (reading 'city')
Optional chaining(user.profile?.city)は、この問題に対するモダンな解決策です。途中のプロパティが存在しない場合でも、エラーを投げる代わりに undefined を返してくれます。詳しくは本章の後半で専用ページを用意しているので、そちらで解説します。
オブジェクトをループで回す
オブジェクトのキーをすべて順番に処理したいときは、Object.keys・Object.values・Object.entries のトリオが定番です。
Object.entries が一番使い勝手がいいです。キーと値を同時に取り出せるうえ、配列の分割代入とも相性抜群です。
for...in ループもありますが、こちらは継承されたプロパティまでたどってしまうので、たいていの場合は意図した挙動になりません。
for (const key in scores) {
console.log(key); // 動作するが、継承されたキーも含まれる
}
Prefer Object.keys / Object.entries unless you specifically want inherited properties.
知っておきたいショートハンド記法
実際のコードで頻繁に見かける糖衣構文を2つ紹介します。
プロパティのショートハンドは、変数名とキー名が一致したときに使えます。一方、計算プロパティ名([expr] という書き方)を使うと、プロパティ名を動的に組み立てられます。フィールド名を引数で受け取って更新するような関数を書くときに便利です。
オブジェクトの等価比較は「参照」で行われる
これは、ほぼ誰もが一度はハマるポイントです。
=== をオブジェクトに使うと、両辺がメモリ上で同じオブジェクトを参照しているかを比較するだけで、中身が同じかどうかは見てくれません。プロパティがまったく同じ2つのオブジェクトでも、別物として扱われます。
「形(構造)が同じか?」を判定したいなら、フィールドを1つずつ比較するか、ディープイコールのヘルパーを使うことになります。お手軽な方法としては JSON.stringify(a) === JSON.stringify(b) がありますが、これはプレーンなデータにしか通用しません。関数や undefined、循環参照が混じった瞬間に破綻します。
const でもオブジェクトは書き換えられる
const は変数を1つの値に固定するだけで、その値が指しているオブジェクト自体を凍結するわけではありません。
本当にイミュータブルにしたいなら、Object.freeze(user) で以降の変更をブロックできます(ただし浅い凍結なので、ネストされたオブジェクトの中身はまだ変更可能です)。とはいえ実務では、Object.freeze に頼るよりも慣習で済ませることがほとんどです。const で宣言したオブジェクトは「この束縛を再代入しない」という意味にとどめて、イミュータビリティは設計レベルでコントロールする、というのが現実的なスタンスですね。
次は配列へ
オブジェクトと配列は、JavaScript のあらゆるデータ構造の土台になる2大要素です。オブジェクトはラベル付きのデータを、配列は順序のあるデータを扱います。次回は JavaScript の配列の仕組みと、日々の処理の大半をこなしてくれる主要メソッド(push、slice、map など)を見ていきましょう。
よくある質問
JavaScriptでオブジェクトを作るにはどうすればいい?
一番よく使うのはオブジェクトリテラル記法です。const user = { name: 'Ada', age: 30 } のように書きます。波括弧の中にキーと値のペアをカンマ区切りで並べるだけ。キーは文字列で、有効な識別子ならクォートは省略可能です。値は数値でも文字列でも、配列、関数、別のオブジェクトでも何でもOKです。
ドット記法とブラケット記法はどう使い分ける?
キーが普通の識別子なら、user.name と user['name'] はまったく同じ動きをします。ブラケット記法を使うのは、キーが変数に入っているとき(user[key])、スペースやハイフンなどドット記法で扱えない文字を含むとき、数字で始まるときなど。それ以外はドット記法のほうがスッキリ書けます。
オブジェクトにプロパティが存在するか確認するには?
プロトタイプ経由で継承されたキーも含めてチェックしたいなら 'name' in user。自身が直接持っているプロパティだけ確認したいなら Object.hasOwn(user, 'name') を使います。これは従来の hasOwnProperty の現代的な代替です。user.name !== undefined でも判定できますが、値として明示的に undefined をセットしている場合に誤判定するので注意してください。
オブジェクトをループで回すにはどうする?
for (const key in obj) でキーを1つずつ取り出せます(継承されたキーも含む)。実務でよく使うのは Object.keys(obj)・Object.values(obj)・Object.entries(obj) に for...of や .forEach() を組み合わせるパターン。キーと値を同時に扱いたいときは Object.entries が便利です。