【JavaScript】プリミティブ型とオブジェクト型を理解したい

#概要

この記事では、JavaScriptのデータ型について理解していきます。JavaScriptにおいて、型の理解は非常に大事なので、しっかり理解しておきたい部分です。

では、いきましょう！



#データ型とは？

そもそもデータ型とはなんなのでしょうか？調べてみると次のような記述がありました。

>プログラミングにおけるデータ型あるいは単に型は、値の種類を示し分類分けするラベルである。データタイプともいう。
*引用：[Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B)*

つまり、簡単にいうと値のカテゴリー分けのことになります。例えば、「10」だったら、数値型のように、各プログラミング言語で決まっています。

もちろん、**JavaScriptにも型は存在します**。




#JavaScriptは型を持っている

「JavaScriptにも型は存在する」と聞いて、「JavaScriptって型がない言語なんじゃないの？」って思っている方がいるかもしれません。これは、間違いです。

「型がある・ない」という表現は、誤解を招きやすいためあまり使わないほうが良いです。

JavaScriptには型が存在します。ここをまず押さえておいてください。



##JavaScriptは動的型付け言語である

なぜ、JavaScriptが型がない言語であると誤解されやすいのでしょうか？

それは、JavaScriptが「**動的型付け言語**」であるためです。

「動的型付け言語」とは、変数や関数の引数・戻り値と言った値の型が、プログラムの実行時の値によって**動的**に変化する言語です。
JavaScriptで変数を宣言する際は、変数名だけを決めますよね。

動的型付け言語と対をなす性質を持った言語を「**静的型付け言語**」と呼びます。

「**静的型付け言語**」では、変数や関数の引数・戻り値と言った値の型をあらかじめ決めておかなければいけません。
例えば、TypeScriptでは、変数を宣言する際に変数名だけではなく、その変数にはどのような型の値が入るのかを指定しておきます。

```js
//JavaScriptによる変数の宣言
let a = 10;

a = 'Messi';
```

```.ts
//TypeScriptによる変数の宣言
let b: number = 10;

b = 'Messi';
//コンパイルエラーになる
```

JavaScriptの例では、`let`で変数`a`を定義し、`10`を代入しています。その後、`a`を`Messi`に書き換えてもエラーにはなりません。
一方、TypeScriptの例では、`let`で変数`b`を定義する際に、`b`の型を数値型に指定しています。そのため、文字列型である'Messi'に値を更新すると、エラーが表示されます。

JavaScriptは「動的型付け言語」であり、「動的型付け言語」では値の型をあらかじめ決めないという性質から、JavaScriptには型がないという誤解をされやすいのです。




#プリミティブ型とオブジェクト型

ここまでで、JavaScriptにも型があるということを理解していただけたと思います。

では、JavaScriptのデータ型はどのように分類されているのでしょうか？

結論から言いますと、JavaScriptのデータ型は「**プリミティブ型**」と「**オブジェクト型**」の2種類に大別されます。

それぞれについて説明します。

簡単にいうと、「プリミティブ型」とは、オブジェクトではないもの、「オブジェクト型」とは、プリミティブ値でないもののことです。

すみません、ちゃんと説明します。

「**プリミティブ型（基本型）**」は、真偽値や数値などの基本的な型のことです。プリミティブ型の値は、一度作成したらその値を変更することができません。（この特性をイミュータブルと呼ぶ）

一方、「**オブジェクト型（複合型）**」は、複数のプリミティブ型の値またはオブジェクトからなる集合体です。オブジェクトは、一度作成した後も、その値自体を変更することができます。（この特性をミュータブルと呼ぶ）

JavaScriptのデータ型は、このような定義で分類されています。




##プリミティブ型とリテラル

プリミティブ型は、さらに以下の7種類に分けられます。

- Boolean型
- Number型
- BigInt型
- String型
- Symbol型
- Null型
- Undefined型

`typeof`演算子を使うことでデータ型を確認することができます。

```.js
console.log(typeof "Messi"); 
console.log(typeof true); 
```

```:出力結果
"string"
"boolean"
```

また、それぞれの型を定義するには「**リテラル（Literal）**」を使います。「リテラル」とは、プログラム上で数値や文字列など、データ型の値を直接記述できるように構文として定義されたものです。
Symbol型とUndefined型以外のプリミティブ型には、リテラルが用意されています。

では、それぞれの型とそのリテラルについて説明していきます。




###Boolean型

**Boolean型**とは、`ture`と`false`の2つの真偽値を扱うデータ型です。
Boolean型には、`true`と`false`の真偽値リテラルがあります。


###Number型

**Number型**とは、数値を扱うためのデータ型です。JavaScriptのNumber型では、整数も少数も同じNumber型として扱われます。扱うことのできる最大値は*2<sup>53</sup>–1*です。
Number型には、`10`や`-7`などの数値リテラルと`1.4`や`5.4e3`などの浮動小数点リテラルがあります。


###BigInt型

**BigInt型**とは、Number型で取り扱うことのできない大きな数値を扱うためのデータ型です。ES2020で追加されました。Number型との互換性はなく、相互に代入や計算・等値比較などは行うことができません。
`200n`などのように数字の後ろに`n`をつけて表現する数値リテラルがあります。


###String型

テキストを表す連続した文字である文字列を扱うためのデータ型です。
`"Messi"`のように、`'`もしくは`"`で囲んだ範囲が文字列リテラルとなります。`` ` ``を用いることで、改行を含む複数行テキストや`${}`による式の展開が可能なテンプレートリテラルになります。


###Symbol型

**Symbol型**とは、「シンボル値」という固有の識別子を表現する値で、任意の不変な値のデータ型です。ES2015で追加されました。
`Symbol()`関数を呼び出すことで同的に生成されます。同じシンボル値を後から生成することはできません。オブジェクトのプロパティキーとして使用することができます。
Symbol型は、リテラル表現を持ちません。


###Null型

**Null型**とは、値が存在しないことを意味するデータ型です。
nullリテラルである`null`は、プリミティブ値`null`を返します。

ただしここで注意点があります。`typeof`演算子で`null`のデータ型を調べると`object`となります。

```.js
console.log(typeof null);
```

```:出力結果
"object"
```

これは、残念なことにJavaScriptの歴史的経緯からなるバグです。



###Undefined型

**Undefined型**とは、値が未定義であることを意味するデータ型です。プリミティブ値`undefined`は、宣言のみが行われた変数やオブジェクト内の存在しないプロパティへのアクセスに割り当てられます。JavaScriptでは、`null`と`undefined`は明確に区別されています。

ここで注意が必要なのは、`undefined`はリテラルではないということです。`undefined`は、ただのグローバル変数で、`undefined`という値を持っているだけです。


以上が、プリミティブ型の分類になります。




##オブジェクト型とリテラル

オブジェクト型には以下のものが含まれます。

- プリミティブ型以外のデータ
- オブジェクト
- 配列
- 関数
- 正規表現
- Data　　　　　　　　など...

基本的にプリミティブ型でないものは、全てオブジェクト型です。

```.js
console.log(typeof ["Messi"]);
console.log(typeof { name: "Messi" });
console.log(typeof function() {});
```

```:出力結果
"object"
"object"
"function"
```

このように、プリミティブ型と同じように`typeof`演算子で分類することができます。しかし、配列とオブジェクトがどちらも`object`と判定されているように、全てのオブジェクトの種類を判定することはできません。
そのため、基本的に`typeof`演算子は、プリミティブ型かオブジェクト型かを判別するために使われます。（`null`のバグには注意しましょう）

オブジェクト型にもリテラルを持つものがあります。

- オブジェクトリテラル
- 配列リテラル
- 正規表現リテラル

それぞれについて説明していきます。



###オブジェクトリテラル

**オブジェクトリテラル**は`{key: value }`の形式で書かれます。`{}`は空のオブジェクトを示します。`obje[key]`もしくは`obj.key`とすることで、任意のプロパティ値にアクセスすることができます。ただし、プロパティ名が識別子として利用できないプロパティ名は、`obj.key`で取り出すことができません。

また、オブジェクトリテラルは、`Object`オブジェクトのインスタンスとして生成されます。

```.js
//どちらでも定義することができる
const obj1 = {};
const obj2 = new Object();
const obj3 = {name: "Messi"};

console.log(obj1, obj2 ,obj3);
```

```:出力結果
{} {} {name: "Messi"}
```

基本的には、オブジェクトリテラルはオブジェクトの作成と同時に中身を定義できるので、`Object`オブジェクトからオブジェクトを作成することはありません。

以下の配列や正規表現もこのオブジェクトが元になっています。



####広義のオブジェクトと狭義のオブジェクト

ここで広義のオブジェクトと狭義のオブジェクトについて説明しておきます。
先ほどから、オブジェクト型だとか、オブジェクトとか一貫性がなく使っているなと思われた方がいるかもしれません。

JavaScriptでは、一般的に連想配列と呼ばれるものもオブジェクトと呼ぶため、オブジェクト型と混同してわかりにくくなっています。ほんとに紛らわしいですよね。
基本的に、JavaScriptの書籍や記事はここの違いについて触れませんので、文脈から推論するしかありません。

それぞれについて簡単に説明すると

- 広義のオブジェクト→最終的な継承元がObjectオブジェクトであるもの
- 狭義のオブジェクト→一般的に「連想配列」と呼ばれるキーとそれに対応するプロパティの集まり

と言えます。



###配列リテラル

**配列リテラル**とは`[1, 2, 3]`の形式で書かれます。`[]`は、空の配列を示します。`arr[n]`という構文で、n-1番目の要素にアクセスすることができます。

また、`Array`オブジェクトのインスタンスとして生成されます。

```.js
const array1 = [];
const array2 = new Array();
const array3 = [1, 2, 3];
const array4 = new Array(1, 2, 3);

console.log(array1, array2 ,array3, array4);
```

```:出力結果
[] [] [1, 2, 3] [1, 2, 3]
```

オブジェクトと同じような理由から、基本的にインスタンスで生成するということはありません。




###正規表現リテラル

**正規表現リテラル**は、`/pattern/ig`の形式で書かれます。正規表現パターンでの特殊文字の使い方については、ここでは割愛しますが、他の言語とほとんど同じです。

また、`RegExp`オブジェクトのインスタンスとして生成されます。


------

以上が、プリミティブ型とオブジェクト型の分類になります。



#プリミティブ値のリテラルでメソッドが使える理由

プリミティブ型の値は、インスタンスメソッドを持たないはずですが、普通の文字列でもメソッドを実行することができてしまいます。

これは、なぜなのでしょうか？

実は、`null`と`undefined`を除く全てのプリミティブ型には、それらの値を抱合する「**ラッパーオブジェクト**」が存在します。例えば、string型なら`String`、number型なら`Number`がラッパーオブジェクトになります。ラッパーオブジェクトは、`new`演算子と対応するコンストラクタ関数を利用して作成することができます。

```.js
//ラッパーオブジェクトを作成
const str = new String("Messi");

console.log(typeof str);
console.log(str.length);
```

```:出力結果
"object"
5
```

`length`は、文字の長さを返すメソッドです。`str`のデータ型はオブジェクト型なので、メソッドを問題なく使うことができます。

JavaScriptでは、明示的にラッパーオブジェクトを記述せずとも、プリミティブ型のデータに対してオブジェクトのように参照する仕組みがあります。
そのため以下のようにプリミティブ型のデータに対してもメソッドを使うことができます。

```.js
//プリミティブ型の変数strを定義
const str = "Messi":

console.log(typeof str);
console.log(str.length);
```

```:出力結果
"string"
5
```

データ型は、プリミティブ型のString型ですが、メソッドが問題なく使用できます。`length`が実行できるのは、自動的に変換された`String`オブジェクトのインスタンスメソッドを実行しているからです。



#まとめ

今回は、JavaScriptのプリミティブ型とオブジェクト型についてまとめてみました。

最後まで読んでいただきありがとうございました。ではまた。