データ型とリテラル
データ型
JavaScriptは動的型付け言語に分類される言語であるため、静的型付け言語のような変数の型はありません。 しかし、文字列、数値、真偽値といった値の型は存在します。 これらの値の型のことをデータ型と呼びます。
データ型を大きく分けると、プリミティブ型とオブジェクトの2つに分類されます。
プリミティブ型(基本型)は、真偽値や数値などの基本的な値の型のことです。 プリミティブ型の値は、一度作成したらその値自体を変更できないというイミュータブル(immutable)の特性を持ちます。 JavaScriptでは、文字列も一度作成したら変更できないイミュータブルの特性を持ち、プリミティブ型の一種として扱われます。
一方、プリミティブ型ではないものをオブジェクト(複合型)と呼び、 オブジェクトは複数のプリミティブ型の値またはオブジェクトからなる集合です。 オブジェクトは、一度作成した後もその値自体を変更できるためミュータブル(mutable)の特性を持ちます。 オブジェクトは、値そのものではなく値への参照を経由して操作されるため、参照型のデータとも言います。
データ型を細かく見ていくと、7つのプリミティブ型とオブジェクトからなります。
- プリミティブ型(基本型)
- 真偽値(Boolean):
true
またはfalse
のデータ型 - 数値(Number):
42
や3.14159
などの数値のデータ型 - 巨大な整数(BigInt): ES2020から追加された
9007199254740992n
などの任意精度の整数のデータ型 - 文字列(String):
"JavaScript"
などの文字列のデータ型 - undefined: 値が未定義であることを意味するデータ型
- null: 値が存在しないことを意味するデータ型
- シンボル(Symbol): ES2015から追加された一意で不変な値のデータ型
- 真偽値(Boolean):
- オブジェクト(複合型)
- プリミティブ型以外のデータ
- オブジェクト、配列、関数、クラス、正規表現、Dateなど
プリミティブ型でないものは、オブジェクトであると覚えていれば問題ありません。
typeof
演算子を使うことで、次のようにデータ型を調べることができます。
console.log(typeof true);// => "boolean"
console.log(typeof 42); // => "number"
console.log(typeof 9007199254740992n); // => "bigint"
console.log(typeof "JavaScript"); // => "string"
console.log(typeof Symbol("シンボル"));// => "symbol"
console.log(typeof undefined); // => "undefined"
console.log(typeof null); // => "object"
console.log(typeof ["配列"]); // => "object"
console.log(typeof { "key": "value" }); // => "object"
console.log(typeof function() {}); // => "function"
プリミティブ型の値は、それぞれtypeof
演算子の評価結果として、その値のデータ型を返します。
一方で、オブジェクトに分類される値は"object"
となります。
配列([]
)とオブジェクト({}
)は、どちらも"object"
という判定結果になります。
そのため、typeof
演算子ではオブジェクトの詳細な種類を正しく判定することはできません。
ただし、関数はオブジェクトの中でも特別扱いされているため、typeof
演算子の評価結果は"function"
となります。
また、typeof null
が"object"
となるのは、歴史的経緯のある仕様のバグ1です。
このことからもわかるようにtypeof
演算子は、プリミティブ型またはオブジェクトかを判別するものです。
typeof
演算子では、オブジェクトの詳細な種類を判定できないことは、覚えておくとよいでしょう。
各オブジェクトの判定方法については、それぞれのオブジェクトの章で見ていきます。
リテラル
プリミティブ型の値や一部のオブジェクトは、リテラルを使うことで簡単に定義できるようになっています。
リテラルとはプログラム上で数値や文字列など、データ型の値を直接記述できるように構文として定義されたものです。
たとえば、"
と"
で囲んだ範囲が文字列リテラルで、これは文字列型のデータを表現しています。
次のコードでは、"こんにちは"
という文字列型のデータを初期値に持つ変数str
を定義しています。
// "と"で囲んだ範囲が文字列リテラル
const str = "こんにちは";
リテラル表現がない場合は、その値を作る関数に引数を渡して作成する形になります。 そのような冗長な表現を避ける方法として、よく利用される主要なデータ型にはリテラルが用意されています。
次の5つのプリミティブ型は、それぞれリテラル表現を持っています。
- 真偽値
- 数値
- BigInt
- 文字列
- null
また、オブジェクトの中でもよく利用されるものに関してはリテラル表現が用意されています。
- オブジェクト
- 配列
- 正規表現
これらのリテラルについて、まずはプリミティブ型から順番に見ていきます。
真偽値(Boolean)
真偽値にはtrue
とfalse
のリテラルがあります。
それぞれはtrue
とfalse
の値を返すリテラルで、見た目どおりの意味となります。
true; // => true
false; // => false
数値(Number)
数値には42
のような整数リテラルと3.14159
のような浮動小数点数リテラルがあります。
これらのリテラルで表現できる数値はIEEE 754の倍精度浮動小数として扱われます。
倍精度浮動小数では64ビットで数値を表現します。
64ビットのうち52ビットを数字の格納のために使い、11ビットを小数点の位置に使い、残りの1ビットはプラスとマイナスの符号です。
そのため、正確に扱える数値の最大値は2^53-1
(2の53乗から1引いた値)となります。
整数リテラル
整数リテラルには次の4種類があります。
- 10進数: 数字の組み合わせ
- ただし、複数の数字を組み合わせた際に、先頭を
0
から開始すると8進数として扱われる場合があります - 例)
0
、2
、10
- ただし、複数の数字を組み合わせた際に、先頭を
- 2進数:
0b
(または0B
)の後ろに、0
または1
の数字の組み合わせ- 例)
0b0
、0b10
、0b1010
- 例)
- 8進数:
0o
(または0O
)の後ろに、0
から7
までの数字の組み合わせ0o
は数字のゼロと小文字アルファベットのo
- 例)
0o644
、0o777
- 16進数:
0x
(または0X
)の後ろに、0
から9
までの数字とa
からf
またはA
からF
のアルファベットの組み合わせ- アルファベットの大文字・小文字の違いは値には影響しません
- 例)
0x30A2
、0xEEFF
0から9の数字のみで書かれた数値は、10進数として扱われます。
console.log(1); // => 1
console.log(10); // => 10
console.log(255); // => 255
0b
からはじまる2進数リテラルは、ビットを表現するのによく利用されています。
b
は2進数を表すbinaryを意味しています。
console.log(0b1111); // => 15
console.log(0b10000000000); // => 1024
0o
からはじまる8進数リテラルは、ファイルのパーミッションを表現するのによく利用されています。
o
は8進数を表すoctalを意味しています。
console.log(0o644); // => 420
console.log(0o777); // => 511
次のように、0
からはじまり、0
から7
の数字を組み合わせた場合も8進数として扱われます。
しかし、この表記は10進数と紛らわしいものであったため、ES2015で0o
の8進数リテラルが新たに導入されました。
また、strict modeではこの書き方は例外が発生するため、次のような8進数の書き方は避けるべきです(詳細は「JavaScriptとは」のstrict modeを参照)。
// 非推奨な8進数の書き方
// strict modeは例外が発生
console.log(0644); // => 420
console.log(0777); // => 511
0x
からはじまる16進数リテラルは、文字のコードポイントやRGB値の表現などに利用されています。
x
は16進数を表すhexを意味しています。
console.log(0xFF); // => 255
// 小文字で書いても意味は同じ
console.log(0xff); // => 255
console.log(0x30A2); // => 12450
名前 | 表記例 | 用途 |
---|---|---|
10進数 | 42 | 数値 |
2進数 | 0b0001 | ビット演算など |
8進数 | 0o777 | ファイルのパーミッションなど |
16進数 | 0xEEFF | 文字のコードポイント、RGB値など |
浮動小数点数リテラル
浮動小数点数をリテラルとして書く場合には、次の2種類の表記が利用できます。
3.14159
のような.
(ドット)を含んだ数値2e8
のようなe
またはE
を含んだ数値
0
からはじまる浮動小数点数は、0
を省略して書くことができます。
.123; // => 0.123
しかし、JavaScriptでは.
をオブジェクトにおいて利用する機会が多いため、
0
からはじまる場合でも省略せずに書いたほうが意図しない挙動を減らせるでしょう。
Note 変数名を数字からはじめることができないのは、数値リテラルと衝突してしまうからです。
e
は指数(exponent)を意味する記号で、e
のあとには指数部の値を書きます。
たとえば、2e8
は2×10の8乗となるので、10進数で表すと200000000
となります。
2e8; // => 200000000
[ES2020] BigInt
JavaScriptでは、1
や3.14159
などの数値リテラルはIEEE 754で定義された倍精度浮動小数となります。
倍精度浮動小数で正確に扱える数値の最大値は2^53-1
(2の53乗から1引いた値である9007199254740991
)です。
この数値リテラルで安全に表せる最大の数値はNumber.MAX_SAFE_INTEGER
として定義されています。
console.log(Number.MAX_SAFE_INTEGER); // => 9007199254740991
数値リテラルで2^53-1
(9007199254740991
)よりも大きな値を表現したり計算すると間違った結果となる場合があります。
この問題を解決するために、ES2020ではBigInt
という新しい整数型のデータ型とリテラルが追加されました。
数値リテラルは倍精度浮動小数(64ビット)で数値を扱うのに対して、BigIntでは任意の精度の整数を扱えます。
そのため、BigIntでは2^53-1
(9007199254740991
)よりも大きな整数を正しく表現できます。
BigIntリテラルは、数値の後ろにn
をつけます。
console.log(1n); // => 1n
// 2^53-1より大きな値も扱える
console.log(9007199254740992n); // => 9007199254740992n
BigIntは整数を扱うデータ型であるため、次のように小数点を含めた場合は構文エラーとなります。
1.2n; // => SyntaxError
[ES2021] Numeric Separators
数値が大きくなるほど、桁数の見間違いなどが発生しやすくなります。 次のコードは、1兆を数値リテラルで書いていますが、桁数を読み取りにくいです。
1000000000000;
ES2021から、数値リテラル内の区切り文字として_
を追加できるNumeric Separatorsがサポートされています。
Numeric Separatorsは、数値リテラル内では区切り文字として_
が追加できます。
次のコードも、1兆を数値リテラルで書いています。数値リテラルを評価する際に_
は単純に無視されるため同じ意味となります。
1_000_000_000_000;
Numeric Separatorsは数値リテラルである整数、浮動小数点、BigIntのリテラル内でのみ利用できます。
また、_
はリテラルの先頭や数値の最後に追加することはできません。
_123; // 変数として評価される
3._14; // => SyntaxError
0x52_; // => SyntaxError
1234n_; // => SyntaxError
文字列(String)
文字列リテラル共通のルールとして、同じ記号で囲んだ内容を文字列として扱います。
文字列リテラルとして次の3種類のリテラルがありますが、その評価結果はすべて同じ"文字列"
になります。
console.log("文字列"); // => "文字列"
console.log('文字列'); // => "文字列"
console.log(`文字列`); // => "文字列"
ダブルクォートとシングルクォート
"
(ダブルクォート)と'
(シングルクォート)はまったく同じ意味となります。
PHPやRubyなどとは違い、どちらのリテラルでも評価結果は同じとなります。
文字列リテラルは同じ記号で囲む必要があるため、次のように文字列の中に同じ記号が出現した場合は、
\'
のように\
(バックスラッシュ)を使ってエスケープしなければなりません。
'8 o\'clock'; // => "8 o'clock"
また、文字列内部に出現しない別のクォート記号を使うことで、エスケープをせずに書くこともできます。
"8 o'clock"; // => "8 o'clock"
ダブルクォートとシングルクォートどちらも、改行をそのままでは入力できません。
次のように改行を含んだ文字列は定義できないため、構文エラー(SyntaxError
)となります。
"複数行の
文字列を
入れたい"; // => SyntaxError: "" string literal contains an unescaped line break
改行の代わりに改行記号のエスケープシーケンス(\n
)を使うことで複数行の文字列を書くことができます。
"複数行の\n文字列を\n入れたい";
シングルクォートとダブルクォートの文字列リテラルに改行を入れるには、エスケープシーケンスを使わないといけません。 これに対してES2015から導入されたテンプレートリテラルでは、複数行の文字列を直感的に書くことができます。
[ES2015] テンプレートリテラル
テンプレートリテラルは、`
(バッククォート)で囲んだ範囲を文字列とするリテラルです。
テンプレートリテラルでは、複数行の文字列を改行記号のエスケープシーケンス(\n
)を使わずにそのまま書くことができます。
複数行の文字列も`
で囲めば、そのまま書くことができます。
`複数行の
文字列を
入れたい`; // => "複数行の\n文字列を\n入れたい"
また、名前のとおりテンプレートのような機能も持っています。
テンプレートリテラル内で${変数名}
と書いた場合に、その変数の値を埋め込むことができます。
const str = "文字列";
console.log(`これは${str}です`); // => "これは文字列です"
テンプレートリテラルも他の文字列リテラルと同様に同じリテラル記号を内包したい場合は、\
を使ってエスケープする必要があります。
`This is \`code\``;// => "This is `code`"
nullリテラル
nullリテラルはnull
値を返すリテラルです。
null
は「値がない」ということを表現する値です。
次のように、未定義の変数を参照した場合は、
参照できないためReferenceError
の例外が投げられます。
foo;// "ReferenceError: foo is not defined"
foo
には値がないということを表現したい場合は、
null
値を代入することで、null
値を持つfoo
という変数を定義できます。
これにより、foo
を値がない変数として定義し、参照できるようになります。
const foo = null;
console.log(foo); // => null
[コラム] undefinedはリテラルではない
プリミティブ型として紹介したundefined
はリテラルではありません。
undefined
はただのグローバル変数で、undefined
という値を持っているだけです。
次のように、undefined
はただのグローバル変数であるため、同じundefined
という名前のローカル変数を宣言できます。
function fn(){
const undefined = "独自の未定義値"; // undefinedという名前の変数をエラーなく定義できる
console.log(undefined); // => "独自の未定義値"
}
fn();
これに対してtrue
、false
、null
などはグローバル変数ではなくリテラルであるため、同じ名前の変数を定義することはできません。
リテラルは変数名として利用できない予約語のようなものであるため、再定義しようとすると構文エラー(SyntaxError)となります。
let null; // => SyntaxError
ここでは、説明のためにundefined
というローカル変数を宣言しましたが、undefined
の再定義は非推奨です。
無用な混乱を生むだけなので避けるべきです。
オブジェクトリテラル
JavaScriptにおいて、オブジェクトはあらゆるものの基礎となります。
そのオブジェクトを作成する方法として、オブジェクトリテラルがあります。
オブジェクトリテラルは{}
(中カッコ)を書くことで、新しいオブジェクトを作成できます。
const obj = {}; // 中身が空のオブジェクトを作成
オブジェクトリテラルはオブジェクトの作成と同時に中身を定義できます。
オブジェクトのキーと値を:
で区切ったものを {}
の中に書くことで作成と初期化が同時に行えます。
次のコードで作成したオブジェクトは key
というキー名と "value"
という文字列の値を持つオブジェクトを作成しています。
キー名には、文字列またはSymbolを指定し、値にはプリミティブ型の値からオブジェクトまで何でも入れることができます。
const obj = {
"key": "value"
};
このとき、オブジェクトが持つキーのことをプロパティ名と呼びます。
この場合、 obj
というオブジェクトはkey
というプロパティを持っていると言います。
obj
のkey
プロパティを参照するには、.
(ドット)でつないで参照する方法と、
[]
(ブラケット)で参照する方法があります。
const obj = {
"key": "value"
};
// ドット記法
console.log(obj.key); // => "value"
// ブラケット記法
console.log(obj["key"]); // => "value"
ドット記法では、プロパティ名が変数名と同じく識別子である必要があります。 そのため、次のように識別子として利用できないプロパティ名はドット記法として書くことができません。
// プロパティ名は文字列の"123"
const object = {
"123": "value"
};
// OK: ブラケット記法では、文字列として書くことができる
console.log(object["123"]); // => "value"
// NG: ドット記法では、数値からはじまる識別子は利用できない
object.123
オブジェクトはとても重要で、これから紹介する配列や正規表現もこのオブジェクトが元となっています。
詳細は「オブジェクト」の章で解説します。
ここでは、オブジェクトリテラル({
と}
)が出てきたら、新しいオブジェクトを作成しているんだなと思ってください。
配列リテラル
オブジェクトリテラルと並んで、よく使われるリテラルとして配列リテラルがあります。
配列リテラルは[
と]
で値をカンマ区切りで囲み、その値を持つArrayオブジェクトを作成します。
配列(Arrayオブジェクト)とは、複数の値に順序をつけて格納できるオブジェクトの一種です。
const emptyArray = []; // 空の配列を作成
const array = [1, 2, 3]; // 値を持った配列を作成
配列は0
からはじまるインデックス(添字)に、対応した値を保持しています。
作成した配列の要素を取得するには、配列に対してarray[index]
という構文で指定したインデックスの値を参照できます。
const array = ["index:0", "index:1", "index:2"];
// 0番目の要素を参照
console.log(array[0]); // => "index:0"
// 1番目の要素を参照
console.log(array[1]); // => "index:1"
配列についての詳細は「配列」の章で解説します。
正規表現リテラル
JavaScriptは正規表現をリテラルで書くことができます。
正規表現リテラルは/
(スラッシュ)と/
(スラッシュ)で正規表現のパターン文字列を囲みます。
正規表現のパターン内では、+
などの特定の記号や\
(バックスラッシュ)からはじまる特殊文字が特別な意味を持ちます。
次のコードでは、数字にマッチする特殊文字である\d
を使い、1文字以上の数字にマッチする正規表現をリテラルで表現しています。
const numberRegExp = /\d+/; // 1文字以上の数字にマッチする正規表現
// `numberRegExp`の正規表現が文字列"123"にマッチするかをテストする
console.log(numberRegExp.test("123")); // => true
RegExp
コンストラクタを使うことで、文字列から正規表現オブジェクトを作成できます。
しかし、特殊文字の二重エスケープが必要になり直感的に書くことが難しくなります。
正規表現オブジェクトについて詳しくは、「文字列」の章で紹介します。
プリミティブ型とオブジェクト
プリミティブ型は基本的にリテラルで表現しますが、真偽値(Boolean)、数値(Number)、文字列(String)はそれぞれオブジェクトとして表現する方法もあります。 これらはプリミティブ型の値をラップしたようなオブジェクトであるためラッパーオブジェクトと呼ばれます。
ラッパーオブジェクトは、new
演算子と対応するコンストラクタ関数を利用して作成できます。
たとえば、文字列のプリミティブ型に対応するコンストラクタ関数はString
となります。
次のコードでは、String
のラッパーオブジェクトを作成しています。
ラッパーオブジェクトは、名前のとおりオブジェクトの一種であるためtypeof
演算子の結果も"object"
です。
また、オブジェクトであるためlength
プロパティなどのオブジェクトが持つプロパティを参照できます。
// 文字列をラップしたStringラッパーオブジェクト
const str = new String("文字列");
// ラッパーオブジェクトは"object"型のデータ
console.log(typeof str); // => "object"
// Stringオブジェクトの`length`プロパティは文字列の長さを返す
console.log(str.length); // => 3
しかし、明示的にラッパーオブジェクトを使うべき理由はありません。
なぜなら、JavaScriptではプリミティブ型のデータに対してもオブジェクトのように参照できる仕組みがあるためです。
次のコードでは、プリミティブ型の文字列データに対してもlength
プロパティへアクセスできています。
// プリミティブ型の文字列データ
const str = "文字列";
// プリミティブ型の文字列は"string"型のデータ
console.log(typeof str); // => "string"
// プリミティブ型の文字列も`length`プロパティを参照できる
console.log(str.length); // => 3
これは、プリミティブ型のデータのプロパティへアクセスする際に、対応するラッパーオブジェクトへ暗黙的に変換してからプロパティへアクセスするためです。 また、ラッパーオブジェクトを明示的に作成するには、リテラルに比べて冗長な書き方が必要になります。 このように、ラッパーオブジェクトを明示的に作成する必要はないため、常にリテラルでプリミティブ型のデータを表現することを推奨します。
このラッパーオブジェクトへの暗黙的な型変換の仕組みについては「ラッパーオブジェクト」の章で解説します。 現時点では、プリミティブ型のデータであってもオブジェクトのようにプロパティ(メソッドなども含む)を参照できるということだけを知っていれば問題ありません。
まとめ
この章では、データ型とリテラルについて学びました。
- 7種類のプリミティブ型とオブジェクトがある
- リテラルはデータ型の値を直接記述できる構文として定義されたもの
- プリミティブ型の真偽値、数値、BigInt、文字列、nullはリテラル表現がある
- オブジェクト型のオブジェクト、配列、正規表現にはリテラル表現がある
- プリミティブ型のデータでもプロパティアクセスができる
1. JavaScriptが最初にNetscapeで実装された際にtypeof null === "object"
となるバグがありました。このバグを修正するとすでにこの挙動に依存しているコードが壊れるため、修正が見送られ現在の挙動が仕様となりました。 詳しくはhttps://2ality.com/2013/10/typeof-null.htmlを参照。 ↩