2022年6月19日

オブジェクト

データ型」の章で学んだように、JavaScriptには8つのデータ型があります。そのうち7つは、「プリミティブ」と呼ばれ、その値は単一のものを含むだけです(文字列や数値など)。

一方、オブジェクトは、さまざまなデータやより複雑なエンティティのキー付きコレクションを格納するために使用されます。JavaScriptでは、オブジェクトは言語のほぼすべての側面に浸透しています。そのため、他のどこかに深く進む前に、まずオブジェクトを理解する必要があります。

オブジェクトは、中括弧 `{...}` を使用して作成でき、オプションでプロパティのリストを含めることができます。プロパティは「キー:値」のペアで、`キー` は文字列(「プロパティ名」とも呼ばれます)、`値` は何でもかまいません。

オブジェクトは、署名されたファイルのあるキャビネットと考えることができます。データの各部分は、キーによってファイルに格納されます。キーでファイルを簡単に検索したり、ファイルを追加/削除したりできます。

空のオブジェクト(「空のキャビネット」)は、2つの構文のいずれかを使用して作成できます。

let user = new Object(); // "object constructor" syntax
let user = {};  // "object literal" syntax

通常は、中括弧 `{...}` が使用されます。この宣言は、オブジェクトリテラルと呼ばれます。

リテラルとプロパティ

「キー:値」のペアとして、いくつかのプロパティを `{...}` にすぐに配置できます。

let user = {     // an object
  name: "John",  // by key "name" store value "John"
  age: 30        // by key "age" store value 30
};

プロパティには、コロン `:` の前にキー(「名前」または「識別子」とも呼ばれます)、その右側に値があります。

`user` オブジェクトには、2つのプロパティがあります。

  1. 最初のプロパティの名前は `“name”` で、値は `“John”` です。
  2. 2番目のプロパティの名前は `“age”` で、値は `30` です。

結果として得られる `user` オブジェクトは、「name」と「age」というラベルが付いた2つの署名されたファイルを含むキャビネットとして考えることができます。

いつでもファイルを追加、削除、読み取ることができます。

プロパティ値には、ドット表記を使用してアクセスできます。

// get property values of the object:
alert( user.name ); // John
alert( user.age ); // 30

値はどのような型でもかまいません。ブール値を追加してみましょう。

user.isAdmin = true;

プロパティを削除するには、`delete` 演算子を使用できます。

delete user.age;

複数単語のプロパティ名も使用できますが、その場合は引用符で囲む必要があります。

let user = {
  name: "John",
  age: 30,
  "likes birds": true  // multiword property name must be quoted
};

リストの最後のプロパティは、コンマで終わる場合があります。

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

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

角括弧

複数単語のプロパティの場合、ドットアクセスは機能しません。

// this would give a syntax error
user.likes birds = true

JavaScriptはそれを理解しません。`user.likes` にアクセスしようとしており、予期しない `birds` が出現すると構文エラーが発生します。

ドットは、キーが有効な変数識別子である必要があります。つまり、スペースを含まず、数字で始まらず、特殊文字を含まない(`$` と `_` は許可されます)。

任意の文字列で動作する代替の「角括弧表記」があります。

let user = {};

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

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

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

これですべて問題ありません。括弧内の文字列は適切に引用符で囲まれていることに注意してください(任意の種類の引用符を使用できます)。

角括弧は、リテラル文字列とは対照的に、変数などから、式の結果としてプロパティ名を取得する方法も提供します。

let key = "likes birds";

// same as 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");

// access by variable
alert( user[key] ); // John (if enter "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, // the name of the property is taken from the variable fruit
};

alert( bag.apple ); // 5 if fruit="apple"

計算済みプロパティの意味は単純です。`[fruit]` は、プロパティ名が `fruit` から取得されることを意味します。

そのため、訪問者が `“apple”` と入力すると、`bag` は `{apple: 5}` になります。

基本的に、これは次のものと同じように機能します。

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

// take property name from the fruit variable
bag[fruit] = 5;

…しかし、より見栄えが良いです。

角括弧内ではより複雑な式を使用できます。

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

角括弧はドット表記よりもはるかに強力です。任意のプロパティ名と変数を許可します。しかし、記述するのもより面倒です。

そのため、ほとんどの場合、プロパティ名が既知で単純な場合は、ドットが使用されます。より複雑なものが必要な場合は、角括弧に切り替えます。

プロパティ値の省略記法

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

たとえば

function makeUser(name, age) {
  return {
    name: name,
    age: age,
    // ...other properties
  };
}

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

上記の例では、プロパティ名は変数と同じです。変数からプロパティを作成するユースケースは非常に一般的であるため、それを短くするための特別なプロパティ値の省略記法があります。

`name:name` の代わりに、`name` と書くことができます。

function makeUser(name, age) {
  return {
    name, // same as name: name
    age,  // same as age: age
    // ...
  };
}

同じオブジェクトで、通常のプロパティと省略記法の両方を使用できます。

let user = {
  name,  // same as name:name
  age: 30
};

プロパティ名の制限

すでに知っているように、変数は「for」、「let」、「return」などの言語予約語と同じ名前を持つことはできません。

しかし、オブジェクトのプロパティにはそのような制限はありません。

// these properties are all right
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"
};

// both alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)

`__proto__` という特殊なプロパティに関する小さな注意点があります。非オブジェクト値に設定することはできません。

let obj = {};
obj.__proto__ = 5; // assign a number
alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intended

コードからわかるように、プリミティブ `5` への代入は無視されます。

`__proto__` の特殊な性質については、以降の章で説明し、このような動作を修正するための方法を提案します。

プロパティの存在テスト、「in」演算子

他の多くの言語と比較してJavaScriptのオブジェクトの注目すべき機能は、任意のプロパティにアクセスできることです。プロパティが存在しない場合でも、エラーは発生しません!

存在しないプロパティを読み取ると、`undefined` が返されるだけです。そのため、プロパティが存在するかどうかを簡単にテストできます。

let user = {};

alert( user.noSuchProperty === undefined ); // true means "no such property"

そのためには、特別な演算子 `“in”` もあります。

構文は次のとおりです。

"key" in object

たとえば

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

alert( "age" in user ); // true, user.age exists
alert( "blabla" in user ); // false, user.blabla doesn't exist

`in` の左側には、プロパティ名が必要です。通常は引用符で囲まれた文字列です。

引用符を省略すると、変数にはテストする実際の名前が含まれている必要があります。たとえば

let user = { age: 30 };

let key = "age";
alert( key in user ); // true, property "age" exists

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

ほとんどの場合、`undefined` との比較は正常に機能します。しかし、それが失敗する特別なケースがあり、`“in”` は正しく機能します。

オブジェクトプロパティが存在するが、`undefined` を格納している場合です。

let obj = {
  test: undefined
};

alert( obj.test ); // it's undefined, so - no such property?

alert( "test" in obj ); // true, the property does exist!

上記のコードでは、プロパティ `obj.test` は技術的には存在します。そのため、`in` 演算子は正しく機能します。

このような状況はめったに起こりません。`undefined` は明示的に割り当てるべきではないからです。「不明」または「空」の値には主に `null` を使用します。そのため、`in` 演算子はコードの中でエキゾチックな存在です。

「for...in」ループ

オブジェクトのすべてのキーを処理するには、`for...in` という特殊な形式のループがあります。これは、以前に学習した `for(;;)` 構文とはまったく異なるものです。

構文

for (key in object) {
  // executes the body for each key among object properties
}

たとえば、`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` を最初にしたいでしょう。

しかし、コードを実行すると、まったく異なる画像が表示されます。

  • 米国(1)が最初に表示されます。
  • 次にスイス(41)などが続きます。

国番号は昇順にソートされます。なぜなら、それらは整数だからです。そのため、`1, 41, 44, 49` が表示されます。

整数プロパティ?それは何ですか?

ここで「整数プロパティ」という用語は、変更せずに整数に変換して戻すことができる文字列を意味します。

そのため、`“49”` は整数プロパティ名です。整数に変換して戻しても、同じままです。しかし、`"+49"` と `“1.2”` はそうではありません。

// Number(...) explicitly converts to a number
// Math.trunc is a built-in function that removes the decimal part
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property
alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property
alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property

…一方、キーが整数でない場合、それらは作成順にリストされます。たとえば

let user = {
  name: "John",
  surname: "Smith"
};
user.age = 25; // add one more

// non-integer properties are listed in the creation order
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
}

これで意図したとおりに機能します。

まとめ

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

プロパティ(キーと値のペア)を格納します。

  • プロパティキーは、文字列またはシンボル(通常は文字列)である必要があります。
  • 値はどのような型でもかまいません。

プロパティにアクセスするには、次のものを使用できます。

  • ドット表記:`obj.property`。
  • 角括弧表記 `obj["property"]`。角括弧を使用すると、変数からキーを取得できます。たとえば、`obj[varWithKey]` です。

追加の演算子

  • プロパティを削除するには:`delete obj.prop`。
  • 指定されたキーを持つプロパティが存在するかどうかを確認するには:`"key" in obj`。
  • オブジェクトを反復処理するには、for (let key in obj)ループを使用します。

この章で学習したものは「プレーンオブジェクト」または単にObjectと呼ばれます。

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

  • Arrayは順序付けられたデータコレクションを格納するために使用します。
  • Dateは日付と時刻に関する情報を格納するために使用します。
  • Errorはエラーに関する情報を格納するために使用します。
  • …など。

これらは後で学習する特殊な機能を持っています。「Array型」や「Date型」と言う人もいますが、正式にはそれ自体が型ではなく、単一の「object」データ型に属し、様々な方法で拡張されています。

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つのプロパティがあればすぐにfalseを返します。

function isEmpty(obj) {
  for (let key in obj) {
    // if the loop has started, there is a property
    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)を作成します。

たとえば

// before the call
let menu = {
  width: 200,
  height: 300,
  title: "My menu"
};

multiplyNumeric(menu);

// after the call
menu = {
  width: 400,
  height: 600,
  title: "My menu"
};

multiplyNumericは何を返す必要もありません。オブジェクトを直接変更する必要があります。

追伸: ここではtypeofを使用して数値かどうかを確認してください。

テストを含むサンドボックスを開きます。

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

テストを含む解答をサンドボックスで開きます。

チュートリアルマップ