JavaScriptのデータ型
「JavaScriptやPHPやRubyって型がない言語でしょ?」と認識されがちですが、これは誤り。
JavaScriptは『動的型付け言語』といって、プログラム実行時の値によって関数の引数や返り値の型が動的に変化するような言語なんですね。
よって、「型はあるけどそれをあまり意識することがない言語」というのが正しいかも。
一方でTypeScriptなどのように、プログラム実行前に関数の引数や返り値の型が予め決まっているのが『静的型付け言語』。
試しにそれぞれの言語で、year
という変数に数値型の2022
と文字列型の2022
を代入してみる。
/* JavaScript */
let year = 2022;
year = '2022';
/* TypeScript */
let year: number = 2022;
year = '2022'; // Type 'string' is not assignable to type 'number'.
JavaScriptは数値型でも文字列型でも代入が可能。(=変数が持つ値の型は動的に変わる)
TypeScriptは数値型で変数宣言したので文字列を代入しようとするとエラーが出る。(=変数が持つ値の型は宣言した型のみ)
JavaScriptもちゃんとデータに型を持っているんだな〜というのが分かったところで、ここからが本題。
データ型は大きく分けてプリミティブ型とオブジェクト型に分けられる。
プリミティブ型とオブジェクト型
プリミティブ型
オブジェクトでなく、インスタンスメソッドを持たないデータ型。
- 論理型(Boolean):true/falseの真偽値を扱う。
- null型:値が存在しないことを明示的に表す。
- undefined型:変数に値が代入されていないことや、アクセスしようとしたプロパティが存在しないことを表す。
- 数値型(Number):整数、浮動小数点、Nan(非数)、Infinity(無限大)を扱う。最大値は2^51-1。
- 長整数型(BigInt):数値型で扱うことが出来る値を超える数値を扱う。ES2020で追加された。
- 文字列型(String):テキストデータを表す。
- シンボル型(Symbol):「シンボル値」という固有の識別子を表す。
const message = 'Hello World!!';
typeof(message) // => string
オブジェクト型
プロパティ(キーと値の関連付け)から成る集合体で、プリミティブ型以外のものを指す。
const message = { text: 'Hello World!!' };
typeof(message) // => object
リテラル
プリミティブ型のデータや一部のオブジェクトに関しては、「こうやって書けばこのデータ型として定義して扱うことが出来るよ」という構文があって、それをリテラルという。
プリミティブ型のリテラル
型 | リテラル | 記法 |
---|---|---|
論理型 | 真偽値リテラル | true または false |
null型 | nullリテラル | null |
undefined型 | - | - |
数値型 | 数値リテラル 浮動小数点リテラル |
数字の組み合わせ (例)2, 99, 1000 小数点を用いた数字の組み合わせ (例)3.14 |
長整数型 | 数値リテラル | 数字の後に n を付ける (例)100n |
文字列型 | 文字列リテラル | ''(シングルクォート)または ""(ダブルクォート)で囲む |
オブジェクト型のリテラル
種類 | リテラル | 記法 |
---|---|---|
Objectオブジェクト | オブジェクトリテラル | { key: value } ( {} は空オブジェクトを表す) |
Arrayオブジェクト | 配列リテラル | [x, y, z, ...] ( [] は空配列を表す) |
RegExpオブジェクト | 正規表現リテラル | /pattern/frags (例)/[a-z]+/i |
Objectオブジェクト?
オブジェクト型のObjectオブジェクトって、何ともややこしい。
どうやらJavaScriptには「広義のオブジェクト」と「狭義のオブジェクト」があるらしい。
「狭義のオブジェクト」にあたるのが、オブジェクトリテラルの構文でkeyとvalueの形で定義した、オブジェクト型の1種類としてのObjectオブジェクト。
「広義のオブジェクト」にあたるのが、プリミティブ型以外の全てのデータを指すオブジェクトという型。
__proto__
というプロパティから、各オブジェクトの継承元を辿ってみる。
/* Objectオブジェクト */
const body = { height: 165, weight: 50 };
body.__proto__ // => {}
body.__proto__.constructor // => [Function: Object]
body.__proto__.constructor.name // => 'Object'
/* Arrayオブジェクト */
const animals = ['cat', 'dog', 'hamster'];
animals.__proto__ // => []
animals._proto__.__proto__ // => {}
animals._proto__.__proto__.constructor // => [Function: Object]
animals._proto__.__proto__.constructor.name // => 'Object'
/* RegExpオブジェクト */
const regex = /[a-z]+/i.;
regex._proto__ // => RegExp {}
regex._proto__.__proto__ // => {}
regex._proto__.__proto__.constructor // => [Function: Object]
regex._proto__.__proto__.constructor.name // => 'Object'
このように、広義のオブジェクトは、Objectという標準組み込みオブジェクトを最終的な継承元に持っている。
「プリミティブ型はインスタンスメソッドを持たない」について
このようなコードを書いたことがある。
const text = 'I live in Tokyo.';
text.replace('Tokyo', 'Osaka'); // => 'I live in Osaka.'
あれ?string型のデータが代入されているプリミティブ型のtext
に対してreplace()
メソッドが使用出来ている?
「プリミティブ型はインスタンスメソッドを持たない」のでは?
これにはラッパーオブジェクトというものが関係しているらしい。
ラッパーオブジェクト
プリミティブ型のデータを内包するオブジェクトのこと。
null型
とundefined型
を除くプリミティブ型のデータは、ラッパーオブジェクトに内包されている。
型 | ラッパーオブジェクト |
---|---|
論理型 | Boolean |
数値型 | Number |
長整数型 | BigInt |
文字列型 | String |
シンボル型 | Symbol |
つまり、先ほどのコード例でプリミティブ型のデータに対してメソッドが使用出来ていたのは、
const text = 'I live in Tokyo.';
ここの部分が
const text = new String('I live in Tokyo.');
「お、プリミティブ型のデータにメソッド使いたいの?使えるようにStringオブジェクトにしておくねー」という風に、JavaScript側で自動変換されていたからなんですね。JavaScriptでは、プリミティブ型の値に対してプロパティアクセスするとき、データ型に対応するラッパーオブジェクトに自動で変換してくれるのだそう。
こういったことも含めて、JavaScriptは型を意識せず使えるんだなーと。
まとめ
- JavaScriptは動的型付け言語であり、データ型は『プリミティブ型』と『オブジェクト型』に大別される
- 『リテラル』というデータ構文を使うことでそれぞれのデータ型を扱える
- プリミティブ型は『ラッパーオブジェクト』に内包されており、必要に応じて自動でオブジェクトに変換される