📌

【JavaScript,TypeScript】オブジェクトのまとめ

2024/12/16に公開

オブジェクト

TypeScriptやJavaScriptでは、{}を使うとプロパティを備えたオブジェクトの定義と生成を簡単に行うことができます。

https://typescriptbook.jp/reference/values-types-variables/object/object-literal

// 空っぽのオブジェクトを生成
const object = {};
 
// プロパティを指定しながらオブジェクトを生成
const person = { name: "Bob", age: 25 };

console.log(object);//{}
console.log(person);//{ name: 'Bob', age: 25 }

{}を使ってオブジェクトを定義する方法をオブジェクトリテラルと呼びます。
オブジェクトのプロパティの変更は次のようにできます。

オブジェクトのコピー

オブジェクトのpersonをperson2に代入して、person2のnameプロパティを変更してみます。
すると、personのnameも変更されてしまいます。

これを防ぐためには、JavaScriptのスプレッド演算子を利用して新しいオブジェクトを生成する方法があります。

このようにして、オブジェクトを新しく定義して使うことで、元のオブジェクトに影響を与えずにコピーしたオブジェクトを変更することができます。

オブジェクトのメソッド

メソッドの定義は次のように記述します。

アロー関数で書くパターン

const person = {
    name: "Bob",
    age: 25,
    sayHello:()=>{console.log("hello")}
};

person.sayHello(); //hello

先頭にfunctionをつけて書くパターン

const person = {
    name: "Bob",
    age: 25,
    sayHello:function(){
       console.log("hello")
    }
};

person.sayHello(); //hello

functionとコロンを省略したパターン

const person = {
    name: "Bob",
    age: 25,
    sayHello(){
       console.log("hello")
    }
};

person.sayHello(); //hello

メソッドの実行

メソッドを定義して実行する

オブジェクトの型注釈

オブジェクトの型注釈は次のような書式で書きます。

const obj:{プロパティ毎の型注釈} = オブジェクトリテラル;

const person: {
    name: string;
    age: number;
    sayHello: () => void;
} = {
    name: "Bob",
    age: 25,
    sayHello: function () {
        console.log("hello");
    }
};

console.log(person);// { name: 'Bob', age: 25, sayHello: [Function: sayHello] }
person.sayHello(); // hello

プリミティブ型の型注釈

プリミティブ型の型注釈の場合は、次のように記述します。

const dog:string = "犬";

型注釈の分離

型エイリアスを使うと、オブジェクトの生成と型情報を分離することができます。

// 型エイリアスの定義
type Person = {
    name: string;
    age: number;
    sayHello: () => void;
};

// オブジェクトの生成
const person: Person = {
    name: "Alice",
    age: 30,
    sayHello: function () {
        console.log(`Hello, my name is ${this.name}`);
    }
};

console.log(person);      // { name: 'Alice', age: 30, sayHello: [Function: sayHello] }
person.sayHello();        // "Hello, my name is Alice"

型エイリアスを使ってオブジェクトを生成した際に、型に定義されているプロパティが未定義のままだとエラーが発生します。

// 型エイリアスの定義
type Person = {
    name: string;
    age: number;
    sayHello: () => void;
};

// オブジェクト生成時に値をセットしない(エラーが発生する)
const person: Person = {
    name: "Alice",
    // age プロパティが欠けているためエラーが発生する
    sayHello: function () {
        console.log(`Hello, my name is ${this.name}`);
    }
};

エラー内容は次のとおりです。

interfaceを使って型注釈

オブジェクトの型情報はinterfaceを使って定義することもできます。

interface Person {
    name: string;
    age: number;
    sayHello: () => void;
};

const person: Person = {
    name: "Alice",
    age: 30,
    sayHello: function () {
        console.log(`Hello, my name is ${this.name}`);
    }
};

console.log(person);      // { name: 'Alice', age: 30, sayHello: [Function: sayHello] }
person.sayHello();        // "Hello, my name is Alice"

読み取り専用のプロパティ

readonly修飾子

オブジェクトの型情報を宣言する際、名前の前にreadonly修飾子を記述したプロパティは読み取り専用になります。

type Person = {
    readonly name: string;
    age: number;
};

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

console.log(person.name); // "Alice"

// 以下のように、nameプロパティを変更しようとするとエラーが発生する
person.name = "Bob";  // エラー: Cannot assign to 'name' because it is a read-only property

as const アサーション

オブジェクトリテラルの後ろにas const を付与したオブジェクトは全てのプロパティが読み取り専用になります。

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

person.age = 31;  // エラー: Cannot assign to 'age' because it is a read-only property

as constを付与しても、オブジェクト全体の書き換えはできてしまいます。

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

// プロパティを変更しようとするとエラー
person.age = 31;  // エラー: Cannot assign to 'age' because it is a read-only property

// オブジェクト全体を再代入することは可能
person = {
    name: "Bob",
    age: 40
} as const;  // これはOK

readonly はネストされたプロパティに適用されない

readonly修飾子は、オブジェクトのプロパティそのものを読み取り専用にすることができますが、オブジェクトのプロパティの下層(ネストされたプロパティ)には適用されません。
つまり、ネストされたオブジェクトのプロパティを読み取り専用にしたい場合は、個別に指定する必要があります。

type Address = {
    city: string;
    country: string;
};

type Person = {
    readonly name: string;
    age: number;
    readonly address: Address; // addressの中身は読み取り専用ではない
};

const person: Person = {
    name: "Alice",
    age: 30,
    address: {
        city: "Tokyo",
        country: "Japan"
    }
};

// 直接のプロパティは読み取り専用
person.name = "Bob";  // エラー: Cannot assign to 'name' because it is a read-only property

// ネストされたプロパティは変更可能
person.address.city = "Osaka";  // これはOK

  • person.name は readonly であるため、変更しようとするとエラーになります。
    ただし、address プロパティ自体には readonly が付いていないため、person.address.city は変更可能です。

下層のプロパティも読み取り専用にしたい場合

ネストされたプロパティもすべて読み取り専用にしたい場合は、readonlyを各階層で明示的に指定します。

type Address = {
    readonly city: string;
    readonly country: string;
};

type Person = {
    readonly name: string;
    readonly age: number;
    readonly address: Address;
};

const person: Person = {
    name: "Alice",
    age: 30,
    address: {
        city: "Tokyo",
        country: "Japan"
    }
};

// 全てのプロパティが読み取り専用
person.address.city = "Osaka";  // エラー: Cannot assign to 'city' because it is a read-only property

as constを使って全体を読み取り専用にする方法もある

as constを使うことでオブジェクト全体(含まれる全ての階層プロパティ)をreadonlyにすることも可能です。

const person = {
    name: "Alice",
    age: 30,
    address: {
        city: "Tokyo",
        country: "Japan"
    }
} as const;

// 全てのプロパティが読み取り専用
person.address.city = "Osaka";  // エラー: Cannot assign to 'city' because it is a read-only property

関数の引数にオブジェクトを渡す際のreadonlyについて

readonly修飾子は、オブジェクト型(Person など)には直接適用できません。このエラーが発生している理由は、readonly が配列やタプルのようなリテラル型に対してしか使えないためです。

type Person = {
    name: string;
    age: number;
};
// 引数にPerson型をreadonlyとして渡せない
function updatePerson(person: readonly Person) {
    person.age = 31;  
}

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

updatePerson(alice);//'readonly' type modifier is only permitted on array and tuple literal types.

解決策

修正方法としては、Person 型そのものに readonly 修飾子を適用して、すべてのプロパティを読み取り専用にするか、引数で readonly を使わずに Person 型のプロパティを個別に readonly にする必要があります。

// readonlyを付与
type Person = {
    readonly name: string;
    readonly age: number;
};
// 引数にreadonlyをつけるのをやめる
function updatePerson(person: Person) {
    person.age = 31;  //ここでプロパティの変更を制限できる
}

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

updatePerson(alice);

もし Person 型のプロパティを変更したくない場合は、readonlyPerson の個々のプロパティに追加するのが最適です。

type Person = {
    name: string;
    age: number;
};

function updatePerson(person: Readonly<Person>) {  // ここで Readonly<T> を使用
    person.age = 31;  // エラー: Cannot assign to 'age' because it is a read-only property
}

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

updatePerson(alice);  

Readonly<T> は TypeScript のユーティリティ型で、オブジェクトのすべてのプロパティを readonly にします。これにより、Person 型をそのままにしておいて、関数の引数として渡されるオブジェクトが変更されないことを保証します。

オプショナルプロパティ

オプショナルプロパティとは、TypeScriptでオブジェクトのプロパティが「あってもなくても良い」ことを示すプロパティのことです。オプショナルプロパティは、プロパティ名の後に?をつけることで定義されます。

type Person = {
    name: string;
    age?: number;  // ageはオプショナルプロパティ
};

const person1: Person = {
    name: "Alice"
};

const person2: Person = {
    name: "Bob",
    age: 25
};

console.log(person1);  // { name: "Alice" }
console.log(person2);  // { name: "Bob", age: 25 }

オプショナルプロパティにアクセスする際は、存在しない可能性があることを考慮する必要があります。

ユーザーのアンケートフォームなどで、任意入力の項目に情報が存在しない場合などのケースで使用することが考えられます。

オプショナルチェーン

プロパティ名の後ろに「?」を付与すると、省略可能な(任意の)プロパティになります。

type Person = {
    name: string;
    address: {
        city: string;
        postalCode: string;
    };
    phone?: {
        number: string;
    };
    birth?:string;
};

const person: Person = {
    name: "Alice",
    address: {
        city: "Tokyo",
        postalCode: "100-0001"
    }
};


console.log(person.address.city);  // "Tokyo"
console.log(person.phone?.number);  // undefined(phoneがないためundefined)
console.log(person.phone?.number ?? "記入なし");  // 記入なし
console.log(person.birth);  // undefined

オブジェクトの分割代入

オブジェクトの分割代入は、JavaScriptやTypeScriptでオブジェクトのプロパティを個別の変数として取り出す方法です。

type Person = {
    name: string;
    address: {
        city: string;
        postalCode: string;
    };
};

const person: Person = {
    name: "Alice",
    address: {
        city: "Tokyo",
        postalCode: "100-0001"
    }
};

const {name:myName,address:{city:myCity,postalCode}} = person;
//同じ記述を下記でもできる
const myName2 = person.name;
const myCity2 = person.address.city;
const postalCode2 = person.address.postalCode; 

console.log(myName,myName2);
console.log(myCity,myCity2);
console.log(postalCode,postalCode2);

出力結果

Alice Alice
Tokyo Tokyo
100-0001 100-0001

このコードの中で、分割代入の記述をしている箇所は次のところです。

const {name:myName,address:{city:myCity,postalCode}} = person;

分割代入の書式として、次のように書くと、別個に変数を定義できます。

{プロパティ,プロパティ2} = オブジェクト;

オブジェクトのプロパティ名とは別に異なる名前の変数に取り出したいときは、変数の後ろに「:変数名」を記述します。

{プロパティ:変数,プロパティ2} = オブジェクト;

分割代入を使わないコードは下記の通りです。

const myName2 = person.name;
const myCity2 = person.address.city;
const postalCode2 = person.address.postalCode; 

分割代入を使うことで、使わないコードよりも短く記述できます。

Discussion