2021年12月15日

オブジェクト

データ型 の章で学んだように、JavaScriptには8つの型があります。そのうち7つは “プリミティブ” と呼ばれています。なぜなら、それらは単一の値のみを持つからです(文字列や数値など何であれ)。

これに対し、オブジェクトは、キー付されたさまざまなデータのコレクションや、より複雑なエンティティを格納するために使用されます。JavaScriptでは、オブジェクトは言語のほぼすべての側面に関連しています。そのため、まず最初にオブジェクトを理解する必要があります。

オブジェクトは波括弧 {…}と任意の プロパティ の一覧から成ります。プロパティは “key:value” のペアで、key は文字列(もしくは"プロパティ名"と呼ばれます)で、value は何でも構いません。

オブジェクトは、署名されたファイルを持つキャビネットとしてイメージすることができます。すべてのデータは、キーによってそのファイルの中に格納されます。ファイルを名前で検索したり、ファイルの追加や削除は簡単です。

空のオブジェクト(“空のキャビネット”)は、次の2つの構文のいずれかで作ることができます:

let user = new Object(); // "オブジェクトコンストラクタ" 構文
let user = {};  // "オブジェクトリテラル" 構文

通常は波括弧 {...} が使われます。その宣言は オブジェクトリテラル と呼ばれます。

リテラルとプロパティ

プロパティを “key: value” のペアの形式で {...} に置くことができます。

let user = {     // オブジェクト
  name: "John",  // キー "name" に値 "John" が格納される
  age: 30        // キー "age" に値 30 が格納される
};

プロパティはコロン ":" の前がキー(“名前” もしくは “識別子” として知られています)で、コロンの右が値です。

ここでは、user オブジェクトは2つのプロパティを持っています:

  1. 最初のプロパティは名前 "name" とその値 "John" です。
  2. 2つ目は、名前 "age" とその値 30 です。

生成された user オブジェクトは、“name” と “age” とラベル付けされた2つのファイルを持つキャビネットと考えることができます。

私たちは、いつでもそこからファイルの追加、削除、参照ができます。

プロパティ値へは、ドット表記を使ってアクセスすることができます。:

// オブジェクトのフィールドを取得:
alert( user.name ); // John
alert( user.age ); // 30

値はどんな型にもなります。boolean の値を追加してみましょう:

user.isAdmin = true;

プロパティを削除するには、delete 演算子を使います:

delete user.age;

また、複数単語からなるプロパティ名を使うこともできます。この場合は引用符で囲む必要があります:

let user = {
  name: "John",
  age: 30,
  "likes birds": true  // 複数語のプロパティ名は引用符で囲まなければなりません
};

リストの最後のプロパティはカンマで終わってもかまいません:

let user = {
  name: "John",
  age: 30,
}

これは、「末尾」または「ぶら下がり」のカンマと呼ばれます。 これがあると、すべての行が同じ表記になるため、プロパティの追加/削除/移動が簡単になります。

角括弧

複数語のプロパティの場合、ドットを使用したアクセスは上手く動作しません:

// これは構文エラーになります
user.likes birds = true

JavaScript はこれを理解することができません。user.likes に対して何か処理をするものと解釈され、その後思いがけない bird によって構文エラーが発生します。

ドットを使用したアクセスを使用するには、有効なプロパティ名である必要があります。具体的には、スペースが含まれていない、数値から始まっていない、特殊文字が含まれていないなどです(ただし、$_ は有効です)。

代わりに、任意の文字列で動作する “角括弧表記” を使います:

let user = {};

// set
user["likes birds"] = true;

// get
alert(user["likes birds"]); // true

// delete
delete user["likes birds"];

これですべて問題ありません。括弧内の文字列は適切に引用符がつけられている(どのようなタイプの引用符でも動作します)ことに留意してください。

角括弧は、任意の式の結果としてプロパティ名を取得する方法も提供します – リテラル文字列とは逆に – 次のようになります:

let key = "likes birds";

// user["likes birds"] = true; と同じ
user[key] = true;

ここで、変数 key は実行時に計算されるかもしれないし、ユーザの入力に依存するかもしれません。そして、プロパティにアクセスするときにそれを使います。これは素晴らしい柔軟性をもたらします。ドット表記の場合、同じようにはできません。

例:

let user = {
  name: "John",
  age: 30
};

let key = prompt("What do you want to know about the user?", "name");

// 変数でアクセス
alert( user[key] ); // John ("name" が入力された場合)

ドット表記は、同じように使用することはできません:

let user = {
  name: "John",
  age: 30
};

let key = "name";
alert( user.key ) // undefined

算出プロパティ

オブジェクトリテラルでは、角括弧を使うことができます。それは 算出プロパティ と呼ばれます。

例:

let fruit = prompt("Which fruit to buy?", "apple");

let bag = {
  [fruit]: 5, // プロパティ名は変数 fruit から取られます
};

alert( bag.apple ); // 5 fruit="apple" の場合

算出プロパティの意味はシンプルです: [fruit] は、プロパティ名は変数 fruit の値になることを意味します。

なので、もし訪問者が "apple" を入力すると, bag{apple: 5} になります。

基本的には、次と同じように動作します。

let fruit = prompt("Which fruit to buy?", "apple");
let bag = {};

// fruit 変数からプロパティ名を取る
bag[fruit] = 5;

…が、よりよく見えます。

角括弧の中では、より複雑な式を使うこともできます。

let fruit = 'apple';
let bag = {
  [fruit + 'Computers']: 5 // bag.appleComputers = 5
};

角括弧はドット表記よりもはるかに強力です。それらは任意のプロパティ名や変数を許容します。しかし、書くのはドットより面倒です。

そのため、プロパティ名を知っていて単純な場合であれば、ドットが使われます。そして、もしより複雑な何かが必要なとき、角括弧に切り替えます。

プロパティの短縮構文

実際のコードでは、既存の変数をプロパティ名の値として使用することがよくあります。

例えば:

function makeUser(name, age) {
  return {
    name: name,
    age: age
    // ...他のプロパティ
  };
}

let user = makeUser("John", 30);
alert(user.name); // John

上の例では、プロパティは変数と同じ名前を持っています。変数からプロパティを作るユースケースでは、非常に一般的です。そして、それを短く書くための特別な プロパティの短縮構文 があります。

name:name の代わりに、このように単に name と書くことができます:

function makeUser(name, age) {
  return {
    name, // name: name と同じ
    age   // age: age と同じ
    // ...
  };
}

同じオブジェクトで、通常のプロパティと短縮構文両方を使うこともできます:

let user = {
  name,  // name:name と同じ
  age: 30
};

プロパティ名の制限

すでにご存知の通り、変数は “for”, “let”, “return” といった、予約語と同じものをもつことはできません。

しかし、オブジェクトプロパティではこのような制限はありません。どんな名前でも大丈夫です:

// これらのプロパティはすべて問題ありません
let obj = {
  for: 1,
  let: 2,
  return: 3
}

alert( obj.for + obj.let + obj.return );  // 6

つまり、プロパティ名には制限がありません。 任意の文字列またはシンボル (後で説明する識別子の特別なタイプ) を使用することができます。

他のタイプの場合は、自動的に文字列に変換されます。

例えば、0 という数値をプロパティに使用すると、文字列の "0" になります。

let obj = {
  0: "test" // same as "0": "test"
};

// 両方とも同じプロパティにアクセスします (数値の 0 は文字列の "0" に変換されます)
alert( obj["0"] ); // test
alert( obj[0] ); // test (同じプロパティ)

__proto__ という特殊なプロパティには落とし穴があります。オブジェクトではない値を設定することができません:

let obj = {};
obj.__proto__ = 5; // 数値を設定
alert(obj.__proto__); // [object Object] - 値はオブジェクトで、意図した通りに動作しません

コードから分かるように、5 の設定は無視されました。

__proto__ の特殊な性質は 後続の章 で説明します。また、このような 動作の修正方法 も提案します。

プロパティ存在チェック, “in” 演算子

他の言語と比べて JavaScript で注目すべきオブジェクトの特徴は、どんなプロパティへもアクセスできることです。プロパティが存在しない場合でもエラーにはなりません!

存在しないプロパティへのアクセスは、単に undefined を返します。なのでプロパティが存在するかは簡単に確認できます。:

let user = {};

alert( user.noSuchProperty === undefined ); // true は "そのようなプロパティはありません" を意味する

プロパティの存在チェックのための特別な演算子 "in" もあります。

構文は次の通りです:

"key" in object

例:

let user = { name: "John", age: 30 };

alert( "age" in user ); // true, user.age は存在する
alert( "blabla" in user ); // false, user.blabla は存在しない

in の左側は プロパティ名 である必要があることに注意してください。通常それは引用符で囲まれた文字列です。

もし引用符を除いた場合、テストされる実際のプロパティ名を持つ変数であることを意味します。例えば:

let user = { age: 30 };

let key = "age";
alert( key in user ); // true, キーから名前を取り、そのプロパティをチェック

なぜ、in 演算子が存在するのでしょうか? undefined と比較するだけで十分ではないでしょうか?

確かに、多くの場合 undefined と比較するだけで正しく動作します。しかし、この方法は特殊なケースで失敗します。一方で、"in" は特殊なケースでも正しく動作します。

それは、オブジェクトのプロパティは存在するが、undefined が格納されているときです:

let obj = {
  test: undefined
};

alert( obj.test ); // これは undefined, なので - このようなプロパティは存在しない?

alert( "test" in obj ); // true, プロパティは存在します!

上のコードでは、プロパティ obj.test は技術的には存在します。なので、 in 演算子は正しく動いています。

このようなシチュエーションは非常にまれです。なぜなら undefined は通常代入されないからです。殆どの場合、“不明” または “空” の値として null を使います。

“for…in” ループ

オブジェクトのすべてのキーを見て回るための、ループの特別な形があります: for..in です。これは以前学んだ for(;;) 構造と完全に異なるものです。

構文:

for(key in object) {
  // オブジェクトプロパティの各キーに対して本体を実行
}

例えば、user のすべてのプロパティを出力してみましょう:

let user = {
  name: "John",
  age: 30,
  isAdmin: true
};

for(let key in user) {
  // keys
  alert( key );  // name, age, isAdmin
  // values for the keys
  alert( user[key] ); // John, 30, true
}

すべての “for” 構造は、ここでの let key のように、ループ内でループする変数を宣言することに留意してください。

また、ここでの key の代わりに、別の変数名を使うこともできます。例えば、"for(let prop in obj)" もまた広く使われています。

オブジェクトの順序付け

オブジェクトは順序付けられますか?つまり、オブジェクトをループするとき、追加したのと同じ順序ですべてのプロパティを取得しますか?それを保証することはできるでしょうか?

回答は、“特別な方法で順序付けられます”: 整数値のプロパティはソートされます、それ以外は作成した順になります。以下、その詳細です。

例として、電話のコードをもつオブジェクトを考えてみましょう:

let codes = {
  "49": "Germany",
  "41": "Switzerland",
  "44": "Great Britain",
  // ..,
  "1": "USA"
};

for(let code in codes) {
  alert(code); // 1, 41, 44, 49
}

オブジェクトはユーザに対してオプションの一覧を提案をするのに使われるかもしれません。もし主にドイツのユーザをターゲットにしたサイトを作る場合、恐らく最初に 49 が出て欲しいです。

しかし、コードを実行すると完全に異なったものが見えます:

  • USA (1) が最初に来ます
  • 次に Switzerland (41) などが並びます.

電話コードは昇順にソートされて表示されます。なぜなら、それらが整数だからです。なので、1, 41, 44, 49 と見えます。

整数プロパティとは?

ここで “整数プロパティ” という用語は、変更なしで整数に変換できる文字列を意味します。

したがって、“49” は整数プロパティ名です。なぜなら、整数の数に変換されて戻っても、それは変わらないからです。 しかし、 "+49"と "1.2"はそうではありません:

// Math.trunc は小数部を取り除く組み込み関数
alert( String(Math.trunc(Number("49"))) ); // "49", 同じ, 整数プロパティ
alert( String(Math.trunc(Number("+49"))) ); // "49", 同じではない ⇒ 非整数プロパティ
alert( String(Math.trunc(Number("1.2"))) ); // "1", 同じではない ⇒ 非整数プロパティ

…一方、もしキーが整数でない場合、作られた順でリストされます。例です:

let user = {
  name: "John",
  surname: "Smith"
};
user.age = 25; // 1つ追加

// 非整数プロパティは作成順にリストされます
for (let prop in user) {
  alert( prop ); // name, surname, age
}

従って、電話コードの問題を直すためには、整数でないコードを作ることで対応することができます。 各コードの前にプラス "+" 記号をつければ十分です。

このように:

let codes = {
  "+49": "Germany",
  "+41": "Switzerland",
  "+44": "Great Britain",
  // ..,
  "+1": "USA"
};

for(let code in codes) {
  alert( +code ); // 49, 41, 44, 1
}

これで意図した通りに動作します。

サマリ

オブジェクトはいくつかの特別な機能を持つ連想配列です。

それらはプロパティ(key-valueペア)を格納します:

  • プロパティのキーは文字列またはシンボル(通常は文字列)です。
  • 値は任意の型になります。

プロパティにアクセスするには、次のようにします:

  • ドット表記: obj.property
  • 角括弧表記: obj["property"]。角括弧は変数からキーを取ることもできます。obj[varWithKey] のように。

追加の演算子:

  • プロパティを削除: delete obj.prop
  • 与えられたキーを持つプロパティの存在チェック: "key" in obj
  • オブジェクトのイテレート: for(let key in obj) ループ

この章で学習したものは、“plain object” または単に Object と呼ばれます。

JavaScriptには他にも多くの種類のオブジェクトがあります:

  • 順序付けされたデータコレクションを格納する Array
  • 日付と時刻に関する情報を格納する Date
  • エラーに関する情報を格納する Error
  • …等々

後で勉強しますが、それらは特別な機能を持っています。また、それらは “Array型” もしくは “Data型” と言われることがありますが、形式的には自身の型ではなく、単一の「オブジェクト」データ型に属しています。 そして、それをさまざまな方法で拡張しています。

Javascript のオブジェクトはとても強力です。ここでは本当に巨大なトピックの表面をなぞっただけです。このチュートリアルの他の部分では、オブジェクトをより深く見ていき、それらについてもっと学んでいきます。

タスク

重要性: 5

アクションごとに1行ずつコードを記述してください。:

  1. 空のオブジェクト user を作ります。
  2. John という値を持つ name プロパティを追加します。
  3. Smith という値を持つ surname プロパティを追加します。
  4. name の値を Pete に変えます。
  5. オブジェクトから name プロパティを削除します。
let user = {};
user.name = "John";
user.surname = "Smith";
user.name = "Pete";
delete user.name;
重要性: 5

オブジェクトがプロパティを持っていない場合に true を、それ以外の場合には false を返す関数 isEmpty(obj) を書きなさい。

このように動く必要があります:

let schedule = {};

alert( isEmpty(schedule) ); // true

schedule["8:30"] = "get up";

alert( isEmpty(schedule) ); // false

テストと一緒にサンドボックスを開く

単にオブジェクトをループし、少なくとも1つプロパティがある場合にはすぐに return false を返します。

function isEmpty(obj) {
  for (let key in obj) {
    return false;
  }
  return true;
}
function isEmpty(obj) {
  for (let key in obj) {
    // if the loop has started, there is a prorty
    return false;
  }
  return true;
}

サンドボックスでテストと一緒に解答を開く

重要性: 5

我々のチームの給与を格納しているオブジェクトがあります。:

let salaries = {
  John: 100,
  Ann: 160,
  Pete: 130
}

すべての給与を合計し変数 sum に格納するコードを書いてください。上の例では 390 になるはずです。

もし salaries が空の場合、結果は 0 である必要があります。

let salaries = {
  John: 100,
  Ann: 160,
  Pete: 130
};

let sum = 0;
for (let key in salaries) {
  sum += salaries[key];
}

alert(sum); // 390
重要性: 3

obj のすべての数値プロパティに 2 を掛ける関数 multiplyNumeric(obj) を作成しなさい。

例:

// 呼び出し前
let menu = {
  width: 200,
  height: 300,
  title: "My menu"
};

multiplyNumeric(menu);

// 呼び出し後
menu = {
  width: 400,
  height: 600,
  title: "My menu"
};

multiplyNumeric は何も返却する必要がないことに注意してください。オブジェクトをその場で変更する必要があります。

P.S. ここでは数値のためのチェックに typeof を使います。

テストと一緒にサンドボックスを開く

function multiplyNumeric(obj) {
  for (let key in obj) {
    if (typeof obj[key] == 'number') {
      obj[key] *= 2;
    }
  }
}

サンドボックスでテストと一緒に解答を開く

チュートリアルマップ