JavaScriptのオブジェクト

オブジェクトの基本とオブジェクトリテラル

JavaScriptは、Javaなどのクラスベース言語とは異なり、クラスを定義しなくても直接オブジェクトを作成することができます。 オブジェクトは、キーと値のペアをまとめたデータ構造です。 JavaScriptにおけるキーのことをプロパティと呼び、値に設定された関数のことをメソッドと呼びます。 中括弧({})を使用するオブジェクトリテラルを用いて、オブジェクトを作成するプログラムは以下のようになります。

object_literal.js

const user = {
  name: "山田太郎",
  age: 20,
  greet: function() {
    console.log(`こんにちは、私は${this.name}です。`);
  }
};

console.log(user.name);
console.log(user.age);
user.greet();

実行結果

山田太郎
20
こんにちは、私は山田太郎です。

オブジェクトのプロパティを参照するには、ドット(.)に続けてプロパティ名を記述します。 メソッドを呼び出す場合も同様にドットを用いて指定し、末尾に括弧を付けて実行します。 メソッドの内部でオブジェクト自身のプロパティにアクセスしたい場合は、this キーワードを使用します。 この this は、メソッドが呼び出された対象のオブジェクトを指し示します。

また、オブジェクト内のメソッドを定義する際、function キーワードとコロン(:)を省略して、より簡潔に記述することもできます。 メソッド定義を簡潔に記述するプログラムは以下のようになります。

object_shorthand.js

const user = {
  name: "山田太郎",
  age: 20,
  // メソッド記述の省略表記
  greet() {
    console.log(`こんにちは、私は${this.name}です。`);
  }
};

user.greet();

実行結果

こんにちは、私は山田太郎です。

プロパティへのアクセス方法

オブジェクトのプロパティやメソッドにアクセスする方法には、ドット記法とブラケット記法の2種類があります。 ブラケット記法([])では、プロパティ名を文字列として指定します。 これを用いることで、変数に保持された文字列を使って動的にプロパティを指定することができます。 2つのアクセス方法を比較するプログラムは以下のようになります。

property_access.js

const user = {
  name: "山田太郎",
  age: 20
};

// ドット記法によるアクセス
console.log(user.name);

// ブラケット記法によるアクセス
const propertyName = "age";
console.log(user[propertyName]);

実行結果

山田太郎
20

通常は簡潔に記述できるドット記法を使用しますが、プログラムの実行中にどのプロパティにアクセスするかが動的に決定される場合には、ブラケット記法を使用します。

オブジェクトの動的な操作

JavaScriptのオブジェクトは非常に柔軟であり、作成された後からでもプロパティやメソッドを自由に追加、変更、削除することができます。 プロパティを削除する場合は、delete 演算子を使用します。 オブジェクトを動的に操作するプログラムは以下のようになります。

dynamic_object.js

const user = {
  name: "山田太郎"
};

// プロパティの追加
user.age = 20;

// メソッドの追加
user.greet = function() {
  console.log(`こんにちは、${this.name}です。`);
};

// プロパティの変更
user.name = "佐藤花子";

user.greet();

// プロパティの削除
delete user.age;
console.log(user.age);

実行結果

こんにちは、佐藤花子です。
undefined

このように、一度作成したオブジェクトに対して、後から構造そのものを変更することができます。 存在しないプロパティにアクセスした場合は、エラーにはならず undefined が返されます。

オブジェクトリテラルによるオブジェクト生成の課題

オブジェクトリテラルを使用すると手軽にオブジェクトを作成できますが、同じ構造を持つオブジェクトを複数作成したい場合に課題が生じます。 例えば、複数のユーザーオブジェクトを管理したい場合、それぞれのオブジェクトに対して同じプロパティやメソッドの定義を記述しなければなりません。 同じ構造のオブジェクトを複数作成するプログラムは以下のようになります。

multiple_objects.js

const user1 = {
  name: "山田太郎",
  age: 20,
  greet() {
    console.log(`こんにちは、私は${this.name}です。`);
  }
};

const user2 = {
  name: "佐藤花子",
  age: 22,
  greet() {
    console.log(`こんにちは、私は${this.name}です。`);
  }
};

user1.greet();
user2.greet();

実行結果

こんにちは、私は山田太郎です。
こんにちは、私は佐藤花子です。

このプログラムでは、オブジェクトの構造や greet メソッドの処理が重複しています。 もしメソッドの処理内容を変更したくなった場合、すべてのオブジェクトに定義されたメソッドを書き換えなければならず、コードの管理が難しくなり、変更漏れによるバグが発生しやすくなります。

改善策:ファクトリ関数による共通化

この重複を解消するための1つのアプローチとして、オブジェクトを生成して戻り値として返す関数であるファクトリ関数を定義する方法があります。 ファクトリ関数を用いてオブジェクトを生成するプログラムは以下のようになります。

factory_function.js

function createUser(name, age) {
  return {
    name: name,
    age: age,
    greet() {
      console.log(`こんにちは、私は${this.name}です。`);
    }
  };
}

const user1 = createUser("山田太郎", 20);
const user2 = createUser("佐藤花子", 22);

user1.greet();
user2.greet();

実行結果

こんにちは、私は山田太郎です。
こんにちは、私は佐藤花子です。

ファクトリ関数を使用することで、オブジェクトの初期化処理やメソッドの定義を1つの関数内に集約することができました。 しかし、このファクトリ関数を用いた手法には、まだ以下の2つの大きな課題が残されています。

  1. メモリ効率の低下: ファクトリ関数を呼び出すたびに、メモリ上に新しい greet メソッド(関数オブジェクト)が作成されます。数千個のユーザーオブジェクトを作成した場合、全く同じ処理を行う関数オブジェクトが数千個も別々にメモリを消費することになります。
  2. オブジェクトの型が不明確: 生成されたオブジェクトはすべて標準の Object クラスから作られた汎用的なオブジェクトになります。プログラム上で、そのオブジェクトが createUser によって作られたものなのか、それとも他の場所で作られたものなのかを判別することができません。

これらの課題を克服し、共通の構造や動作を効率的に定義するために用意されている仕組みが、次章で学習するクラス構文や、その背後にあるプロトタイプです。


演習

演習1

以下の仕様を満たす、商品を表す product オブジェクトをオブジェクトリテラルを用いて作成しなさい。

  • プロパティとして、商品名を表す name(文字列型)と、税抜価格を表す price(数値型)を持つこと。
  • メソッドとして、消費税を10%として計算した税込価格を返す getTaxIncludedPrice() を持つこと。
  • 作成したオブジェクトの getTaxIncludedPrice() メソッドを実行し、税込価格が正しく出力されることを確認しなさい。

演習2

演習1で作成した商品オブジェクトを生成するためのファクトリ関数である createProduct(name, price) を作成しなさい。

  • 引数として nameprice を受け取り、演習1と同じプロパティおよびメソッドを持ったオブジェクトを生成して返すこと。
  • 作成したファクトリ関数を用いて2つの異なる商品オブジェクトを作成し、それぞれの税込価格が正しく計算されることを確認しなさい。

演習の解答例

演習1の解答例

オブジェクトリテラルを用いて商品オブジェクトを定義する実装例は以下の通りである。 メソッドの内部で自身のプロパティである price にアクセスするために this.price を使用し、1.1倍した値を返している。

product_literal.js

const product = {
  name: "ノートパソコン",
  price: 100000,
  getTaxIncludedPrice() {
    return this.price * 1.1;
  }
};

console.log(product.getTaxIncludedPrice());

実行結果

110000

演習2の解答例

ファクトリ関数を用いて商品オブジェクトを動的に生成する実装例は以下の通りである。 引数で受け取った商品名と価格を設定したオブジェクトを戻り値として返し、税込価格を求めるメソッドも関数内で定義している。

product_factory.js

function createProduct(name, price) {
  return {
    name: name,
    price: price,
    getTaxIncludedPrice() {
      return this.price * 1.1;
    }
  };
}

const product1 = createProduct("スマートフォン", 80000);
const product2 = createProduct("ワイヤレスイヤホン", 15000);

console.log(product1.getTaxIncludedPrice());
console.log(product2.getTaxIncludedPrice());

実行結果

88000
16500

results matching ""

    No results matching ""