🦔

JavaScriptのオブジェクト、知っておくべき基本とトリック

2024/08/10に公開

はじめに

フロントエンド開発において、オブジェクトを扱うことは非常に多く、さまざまな使い方があります。一方で使い方が多いからこそ、十分に使いこなせていないと感じていました。そこで今回はオブジェクトに関する基本的な使い方や特性、そして注意点についてまとめてみました。

オブジェクトとは

そもそもオブジェクトとはなんでしょう?
JavaScriptにおけるオブジェクトとは、プロパティ(キーと値のペア)の集合体です。プロパティの値には、number型やstring型といったプリミティブ型に加えて、関数や他のオブジェクトを含めることができます。

const macbook = {
  model: "MacBook Air M3",
  brand: "Apple",
  price: 130000,
  getWarrantyExpirationDate: function () {
    // 現在の年から2年間の保証期間を加算
    const currentDate = new Date();
    const expirationDate = new Date(currentDate.setFullYear(currentDate.getFullYear() + 2));
    return expirationDate;
  },
  releaseDate: new Date("2024-03-15"),
};

// 使用例
console.log(macbook.model); // 'MacBook Air M3'
console.log(macbook.getWarrantyExpirationDate()); // 保証の期限日を表示
console.log(macbook.releaseDate); // Fri Mar 15 2024 00:00:00 GMT+0900 (日本標準時)

基本的な使い方

以下は、Object型の一般的な使い方です。
※この章は基本的なオブジェクトの使い方を説明しているため、既にご存知の方は読み進めていただいても大丈夫です!

1. オブジェクトの作成とプロパティの定義

オブジェクトは、キーと値のペアを持つプロパティを格納できます。オブジェクトリテラルを使用して簡単に作成できます。

// オブジェクトの作成
const person = {
  firstName: 'John',
  lastName: 'Doe',
  age: 30
};

// プロパティのアクセス
console.log(person.firstName); // 'John'(ドット記法でアクセス)
console.log(person['age']); // 30(ブラケット記法でアクセス)

2. プロパティの追加、変更、削除

オブジェクトのプロパティは、動的に追加、変更、削除できます。

const car = {
  brand: 'Toyota',
  model: 'Corolla'
};

// プロパティの追加
car.year = 2021;

// プロパティの変更
car.model = 'Camry';

// プロパティの削除
delete car.year;

console.log(car); // { brand: 'Toyota', model: 'Camry' }

3. ネストされたオブジェクト

オブジェクトは、他のオブジェクトをプロパティとして持つことができます。これをネストされたオブジェクトと呼びます。

const employee = {
  name: 'Alice',
  position: 'Developer',
  address: {
    street: '123 Main St',
    city: 'New York'
  }
};

console.log(employee.address.city); // 'New York'

4. オブジェクトの反復処理

オブジェクトのプロパティを反復処理するためには、for...inループやObject.keys()Object.values()Object.entries()を使用します。

const person = {
  name: 'Bob',
  age: 25,
  occupation: 'Engineer'
};

// for...inループでプロパティを列挙
for (const key in person) {
  console.log(`${key}: ${person[key]}`);
}

// Object.keys()でプロパティ名を取得
const keys = Object.keys(person);
console.log(keys); // ['name', 'age', 'occupation']

// Object.values()でプロパティ値を取得
const values = Object.values(person);
console.log(values); // ['Bob', 25, 'Engineer']

// Object.entries()でキーと値のペアを取得
const entries = Object.entries(person);
console.log(entries); // [['name', 'Bob'], ['age', 25], ['occupation', 'Engineer']]

5. オブジェクトのクローンとマージ

オブジェクトのコピーやマージを行うためには、スプレッド演算子(...)やObject.assign()を使用します。

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };

// スプレッド演算子を使用してマージ
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { a: 1, b: 3, c: 4 }

// Object.assign()を使用してマージ
const assignedObj = Object.assign(obj1, obj2);
console.log(assignedObj); // { a: 1, b: 3, c: 4 }

// オブジェクトのクローン
const cloneObj = { ...obj1 };
console.log(cloneObj); // { a: 1, b: 2 }

6. プロトタイプチェーン

すべてのJavaScriptオブジェクトは、プロトタイプチェーンを持ち、他のオブジェクトからプロパティやメソッドを継承します。プロトタイプの利用や設定もできます。

const animal = {
  species: 'Unknown'
};

const dog = Object.create(animal);
dog.breed = 'Labrador';

console.log(dog.species); // 'Unknown'
console.log(dog.breed); // 'Labrador'

7. API 実行時の設定

アプリケーションの設定をオブジェクトで管理することで、設定の変更が容易になります。特に複数の設定オプションを扱う場合に便利です。

const config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer token"
  }
};

// APIリクエストを行う関数
function fetchData(endpoint) {
  return fetch(`${config.apiUrl}/${endpoint}`, {
    method: 'GET',
    headers: config.headers,
    timeout: config.timeout
  }).then(response => response.json());
}

fetchData("data").then(data => console.log(data));

8. 分割代入

オブジェクト内の複数のプロパティを一度に変数に代入したい場合は、分割代入を利用することができます。

const person = { name: 'Alice', age: 30, city: 'Tokyo' };

// オブジェクトのプロパティを変数に代入
const { name, age, city } = person;

console.log(name); // 'Alice'
console.log(age);  // 30
console.log(city); // 'Tokyo'

// デフォルト値の設定
const { country = 'Japan' } = person;
console.log(country); // 'Japan'(デフォルト値が適用される)

ちょっとした注意点

これまでオブジェクトの基本的な使い方について説明してきましたが、実際に利用する際には注意が必要です。
通常、constで宣言した定数の値は変更できません。例えば、myNameという定数に別の値を再代入しようとするとエラーが発生します。しかし、オブジェクトをconstで宣言しても、そのプロパティは変更可能です。これはどうしてなのでしょうか?

/**
 * constで更新を実行するとエラーになる
 */
const myName = "Bill"
myName = "Alice"  // Cannot assign to 'myName' because it is a constant.

/**
 * オブジェクトの場合、更新してもエラーにならない
 */
const person = { name: "Alice", age: 25 };

// プロパティの変更可能
person.name = "Bob";
person.age = 30;

この仕組みを理解するためにはまず値の参照について理解する必要があります。

値の参照

constは定数の再代入を防ぐためのものであり、定数が指すメモリの場所が不変であることを保証します。つまり、定数がどのオブジェクトを参照するかは変更できません。一方でオブジェクトのプロパティ(キーと値のペア)は変更可能です。これは、constがオブジェクトの参照自体を固定するだけで、その中身(プロパティ)までは固定しないためです。

/**
 * オブジェクト自体の更新は不可能
 */
const myObject = { name: "Alice" };
myObject = { name: "Bob" }; // エラー: Assignment to constant variable.

/**
 * プロパティの更新は可能
 */
const person = { name: "Alice", age: 25 };

// プロパティの変更
person.name = "Bob";
person.age = 30;

// プロパティの追加
person.email = "bob@example.com";

console.log(person); // { name: "Bob", age: 30, email: "bob@example.com" }

プロパティ自体の更新を防ぐ方法

それでは、プロパティまで固定したい場合はどうすれば良いでしょうか?
オブジェクトのプロパティ自体の更新を防ぐには、JavaScriptのObject.freeze()Object.seal()Object.preventExtensions()メソッドを使用することでプロパティ自体の更新を防ぐことができます。
それぞれのメソッドについて説明します。

1. Object.freeze()

Object.freeze()は、オブジェクトを凍結して、そのプロパティの追加、削除、変更を防ぎます。プロパティの値も変更できなくなります。

const person = {
  name: "Alice",
  age: 30
};

// オブジェクトを凍結
Object.freeze(person);

// プロパティの変更はできない
person.name = "Bob"; // 無視されるか、厳格モードではエラーになる
person.email = "alice@example.com"; // 無視されるか、厳格モードではエラーになる

console.log(person); // { name: "Alice", age: 30 }

2. Object.seal()

Object.seal()は、オブジェクトに新しいプロパティの追加を防ぎますが、既存のプロパティの変更は許可します。削除もできません。

const car = {
  brand: "Toyota",
  model: "Corolla"
};

// オブジェクトをシール(封印)
Object.seal(car);

// 既存のプロパティの変更は可能
car.model = "Camry";

// 新しいプロパティの追加はできない
car.year = 2023; // 無視されるか、厳格モードではエラーになる

// プロパティの削除もできない
delete car.model; // 無視されるか、厳格モードではエラーになる

console.log(car); // { brand: "Toyota", model: "Camry" }

3. Object.preventExtensions()

Object.preventExtensions()は、オブジェクトに新しいプロパティを追加するのを防ぎますが、既存のプロパティの変更や削除は許可します。

const book = {
  title: "JavaScriptの教科書",
  author: "山田太郎"
};

// オブジェクトの拡張を防ぐ
Object.preventExtensions(book);

// 新しいプロパティの追加はできない
book.price = 2000; // 無視されるか、厳格モードではエラーになる

// 既存のプロパティの変更は可能
book.title = "JavaScriptマスターガイド";

// プロパティの削除は可能
delete book.author;

console.log(book); // { title: "JavaScriptマスターガイド" }

一覧表

メソッド 新規プロパティの追加 既存プロパティの変更 既存プロパティの削除
Object.freeze() 不可 不可 不可
Object.seal() 不可 可能 不可
Object.preventExtensions() 不可 可能 可能

freeze()の注意点

プロパティの更新を防ぐ場合も注意が必要です。
Object.freeze()は浅い凍結を行うという特徴を待ちます。つまり、オブジェクトの第一階層のプロパティは変更できなくなりますが、ネストされたオブジェクトや配列の内部は凍結されません。ネストされたオブジェクトや配列の内容は、依然として変更可能です。

const obj = Object.freeze({
  name: "Alice",
  address: {
    city: "Tokyo"
  }
});

obj.name = "Bob"; // 変更できない
obj.address.city = "Osaka"; // 変更可能

console.log(obj.address.city); // 'Osaka'

もし、ネストされたオブジェクトもfreezeさせたい場合はネストの箇所で再度freezeを実行する必要があります。レアケースかもしれませんが、ネストが深すぎる場合は、別途freezeを実行する再帰関数を書いても良いかもしれませんね。

const student = Object.freeze({
  id: "1234",
  name: "John Doe",
  age: 30,
  // ネストされたオブジェクトも凍結
  address: Object.freeze({
    preference: "New York",
    block: "Manhattan",
  }),
});

// プロパティの変更や追加は無視されるか、厳格モードではエラーになる
student.name = "鈴木"; // 無視されるか、厳格モードではエラーになる
student.address.preference = "Osaka"; // 無視されるか、厳格モードではエラーになる
student.address.block = "Shibuya"; // 無視されるか、厳格モードではエラーになる

console.log(student.name); // 'John Doe'
console.log(student.address.preference); // 'New York'
console.log(student.address.block); // 'Manhattan'

まとめ

今回はオブジェクトについてまとめました。オブジェクトは使用頻度が高いので理解度を高めて良いコードを書いていきたいですね。正直なところ、Object.freeze()は現場で使ったことがなかったので、実際のユースケースなどについて教えていただけると嬉しいです!

参考資料

https://jsprimer.net/basic/object/
https://typescriptbook.jp/reference/values-types-variables/object
https://stackoverflow.com/questions/74737846/what-are-the-usecases-for-object-freeze-and-object-seal-in-javascript
https://qiita.com/yuta0801/items/f8690a6e129c594de5fb
https://lorem-co-ltd.com/wrong-object-freeze/

Discussion