JavaScriptの基礎 - プリミティブ型
概要
JavaScriptには、7つのプリミティブ型 が存在する。
これは、string (文字列)、number (数値)、bigint (大整数)、boolean (真偽値)、null、undefined、symbol (シンボル) である。
プリミティブ型はオブジェクト型と対比される概念であり、"オブジェクトではなく、メソッドやプロパティを持たないデータ" と定義される。
全てのプリミティブ型は共通してイミュータブル (不変) という特性を持つ。
1度作成されたプリミティブ値は変更できない。変数を再割り当てすることはできるが、既存の値そのものは変わらない。
また、プリミティブ型は値渡しの特性を持つ。
変数に別の変数を代入すると、値そのものがコピーされる。
その後、どちらかの変数を変更しても、もう一方には影響しない。
型の確認には typeof 演算子を使用する。
ただし、typeof null が "object" を返すという歴史的なバグが存在するため、nullのチェックには === null を使用する必要がある。
JavaScriptは動的型付け言語であり、変数の型は実行時に決まる。
また、演算子やコンテキストによって型が自動変換される 型強制 (type coercion) が発生することがある。
この暗黙的な型変換の挙動を理解することは、予期しないバグを避けるために重要である。
プリミティブ型の一覧
JavaScriptの7つのプリミティブ型を以下に示す。
| 型名 | typeofの結果 | ラッパーオブジェクト | 説明 |
|---|---|---|---|
| string | "string" | String | UTF-16エンコードされたテキスト |
| number | "number" | Number | IEEE 754倍精度浮動小数点数 |
| bigint | "bigint" | BigInt | 任意精度の整数 (ES2020) |
| boolean | "boolean" | Boolean | true または false |
| null | "object" (歴史的バグ) | なし | オブジェクト値の意図的な不在 |
| undefined | "undefined" | なし | 値の不在 (未割り当て) |
| symbol | "symbol" | Symbol | ユニークで不変の識別子 (ES2015) |
string (文字列)
string型は、UTF-16エンコードされたテキストを表すプリミティブ型である。
ダブルクォート (")、シングルクォート (')、またはバックティック (`) のいずれかで囲んで表現する。
文字列はイミュータブルであり、1度作成した文字列の内容を変更することはできない。
文字列メソッドは元の文字列を変更せず、常に新しい文字列を返す。
// 文字列リテラルの表記方法
const str1 = "Hello, World!";
const str2 = 'Hello, World!';
const name = "Alice";
const str3 = `Hello, ${name}!`; // テンプレートリテラル: "Hello, Alice!"
// イミュータブル性の確認
const str = "hello";
str.toUpperCase(); // "HELLO" を返すが、str は変わらない
console.log(str); // "hello" のまま
// 文字列の長さ
console.log("hello".length); // 5
number
number (数値) 型は、IEEE 754倍精度 (64ビット) 浮動小数点形式で表現される数値を扱うプリミティブ型である。
JavaScriptは整数と浮動小数点数を区別せず、全ての数値をこの単一の型で表現する。
number型には、通常の数値に加えて以下の特殊な値が存在する。
- NaN (Not-a-Number)
- 数値として表現できない計算の結果を示す。
NaN === NaNはfalseになるという特殊な性質があるため、Number.isNaN()を使用して確認する。
- Infinity / -Infinity
- 数値が表現可能な範囲を超えた場合に得られる値。
- Number.MAX_SAFE_INTEGER / Number.MIN_SAFE_INTEGER
- 安全に表現できる整数の上限 ( ) と 下限 ( ) を示す定数。
// 基本的な数値
const integer = 42;
const float = 3.14;
const negative = -100;
// 特殊な値
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log("hello" - 1); // NaN
// NaN の確認
console.log(NaN === NaN); // false (特殊な性質)
console.log(Number.isNaN(NaN)); // true
// 浮動小数点精度の問題
console.log(0.1 + 0.2); // 0.30000000000000004
// 安全な整数の範囲
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992 (精度が失われる)
bigint
bigint (大整数) 型は、任意精度の整数を表現できるプリミティブ型である。
ES2020で導入された。
number型では安全に扱えない Number.MAX_SAFE_INTEGER を超える大きな整数の計算に使用する。
bigintリテラルは、整数値の末尾に n を付けて表記する。
BigInt() コンストラクタで変換することも可能である。
bigint型にはいくつかの重要な制限がある。
- number型との混在不可
- bigintとnumberを直接演算すると、TypeError が発生する。
- 浮動小数点非対応
- bigintは整数のみを扱う。
- 小数点以下の値を持つ数値から変換しようとすると RangeError が発生する。
- JSON非対応
JSON.stringify()は、bigintを含むオブジェクトをそのままシリアライズできない。
// bigintリテラル
const big1 = 123n;
const big2 = 9007199254740999n;
// BigInt()コンストラクタ
const big3 = BigInt(Number.MAX_SAFE_INTEGER); // 9007199254740991n
// number型では精度が失われる計算が正確に行える
console.log(big3 + 1n); // 9007199254740992n
console.log(big3 + 1n === big3 + 2n); // false (bigint は正確)
// number との混在はエラー
// console.log(42 + 1n); // TypeError: Cannot mix BigInt and other types
// 比較演算子は使用可能
console.log(1n < 2); // true
console.log(2n > 1); // true
boolean (真偽値)
boolean型は、true または false の2つの値のみを持つプリミティブ型である。
条件分岐やループ等の制御フローに使用される。
JavaScriptでは、boolean値だけでなく、あらゆる値が条件文の中で真 または 偽として評価される。
// booleanリテラル
const isActive = true;
const isDone = false;
// 論理演算子
console.log(true && false); // false
console.log(true || false); // true
console.log(!true); // false
truthy / falsyな値
条件文の中で false として評価される値をfalsy値、true として評価される値をtruthy値と呼ぶ。
falsy値は以下の8つのみである。
それ以外の全ての値は、truthyとして評価される。
| 値 | 型 | 説明 |
|---|---|---|
false |
boolean | booleanのfalse |
0 |
number | 数値のゼロ |
-0 |
number | 負のゼロ |
0n |
bigint | BigIntのゼロ |
"" |
string | 空文字列 (シングルクォートやテンプレートリテラルの空文字も同様) |
null |
null | null |
undefined |
undefined | undefined |
NaN |
number | Not-a-Number |
注意が必要なtruthy値の例を以下に示す。
"0"(文字列の "0")- 空でない文字列は全てtruthyである。
- これは、数値の0とは異なる。
"false"(文字列の "false")- 空でない文字列なのでtruthyである。
[](空配列)- JavaScriptでは空の配列もオブジェクトと同様にtruthyである。
- これは、Pythonとは異なる点に注意する。
{}(空オブジェクト)- 空のオブジェクトもtruthyである。
Infinity/-Infinity- ゼロでない数値なのでtruthyである。
// truthy値の確認
if ("0") { console.log("実行される"); } // "0" は truthy
if ([]) { console.log("実行される"); } // [] は truthy
if ({}) { console.log("実行される"); } // {} は truthy
// falsy値の確認
if (0) { console.log("実行されない"); }
if ("") { console.log("実行されない"); }
if (null) { console.log("実行されない"); }
null
nullは、オブジェクト値の意図的な不在を表すプリミティブ値である。
変数が 意図的に空である ことを示す時に使用する。
開発者が明示的に設定する値である。
nullはキーワードであり、識別子ではない。
// null の使用例
let user = null; // まだユーザが存在しないことを明示
// nullのチェック (=== を使用)
console.log(user === null); // true
// nullはfalsy
if (!null) {
console.log("nullはfalsy"); // 実行される
}
// 数値演算でのnullの扱い
console.log(1 + null); // 1 (null は 0 に変換される)
typeofの注意点
typeof null は "object" を返す。
これは、JavaScriptの歴史的なバグであり、仕様上の誤りである。
この問題の背景として、JavaScriptの初期実装では値が32ビット単位で格納されており、型を示す1〜3ビットの型タグと実際のデータで構成されていた。
オブジェクトの型タグは0であり、nullはNULLポインタ (0x00) として表現されていたため、nullの型タグも0となり、"object" が返されるようになった。
2013年にこの動作を修正する提案が行われたが、既存のコードとの後方互換性の問題から却下された。
そのため、nullであるかを確認するには、typeof ではなく、=== null を使用する必要がある。
// typeof nullの歴史的バグ
console.log(typeof null); // "object" (バグ: 本来は "null" であるべき)
// nullのチェックには === null を使用する
const value = null;
console.log(value === null); // true (正しい方法)
// オブジェクトかどうかを確認する場合は、nullチェックも追加する
if (value !== null && typeof value === "object") {
console.log("これは本当のオブジェクト");
}
undefined
undefined は、値の不在を表すプリミティブ値である。
変数が宣言されたが値が割り当てられていない場合、関数が明示的な戻り値を返さない場合等に自動的に割り当てられる。
// 未割り当ての変数
let x;
console.log(x); // undefined
// 関数が値を返さない場合
function doNothing() {}
console.log(doNothing()); // undefined
// オブジェクトに存在しないプロパティへのアクセス
const obj = { name: "Alice" };
console.log(obj.age); // undefined
// typeof undefined
console.log(typeof undefined); // "undefined"
nullとの違い
null と undefined はどちらも 値がない ことを表すが、意味が異なる。
| 特性 | null | undefined |
|---|---|---|
| 意味 | オブジェクト値の意図的な不在 | 値の不在 (未割り当て) |
| 設定者 | 開発者が明示的に設定 | JavaScriptエンジンが自動的に割り当て |
| typeof の結果 | "object" (歴史的バグ) |
"undefined"
|
厳密等価 (===) による比較 |
null === undefined -> false |
undefined === null -> false
|
緩い等価 (==) による比較 |
null == undefined -> true |
undefined == null -> true
|
| 数値への変換 | Number(null) -> 0 |
Number(undefined) -> NaN
|
| JSON シリアライズ | "null" として出力される |
プロパティごと省略される |
// 厳密等価 (===) では false
console.log(null === undefined); // false
// 緩い等価 (==) では true (nullish同士)
console.log(null == undefined); // true
// 数値変換の違い
console.log(1 + null); // 1 (null は 0 に変換)
console.log(1 + undefined); // NaN (undefined は NaN に変換)
// JSONシリアライズの違い
JSON.stringify({ a: null, b: undefined }); // '{"a":null}'
symbol
symbol (シンボル) 型は、ユニークで不変の識別子を表すプリミティブ型である。ES2015 (ES6) で導入された。
Symbol() を呼び出すたびに、一意の新しいシンボルが生成される。
たとえ同じ説明文 (description) を渡しても、生成されるシンボルは互いに異なる。
主な用途として、オブジェクトプロパティのキーとして使用することで、名前の衝突を防ぐことが挙げられる。
シンボルをキーとするプロパティは for...in や Object.keys() には現れないため、外部から意図せずアクセスされるリスクを低減できる。
// Symbolの生成
const sym1 = Symbol("description");
const sym2 = Symbol("description");
console.log(sym1 === sym2); // false (同じ説明でも異なるシンボル)
// Symbol.for() によるグローバルレジストリ
const globalSym1 = Symbol.for("global");
const globalSym2 = Symbol.for("global");
console.log(globalSym1 === globalSym2); // true (同じキーなら同じシンボル)
// オブジェクトプロパティのキーとして使用
const ID = Symbol("id");
const user = {
name: "Alice",
[ID]: 12345
};
console.log(user[ID]); // 12345
console.log(Object.keys(user)); // ["name"] (シンボルは列挙されない)
// well-known Symbolの例
// Symbol.iterator でカスタムイテレータを実装できる
typeof演算子
typeof 演算子は、オペランドの型を表す文字列を返す演算子である。
typeof <オペランド>
各プリミティブ型に対する typeof の結果を以下に示す。
| 式 | 結果 | 備考 |
|---|---|---|
| typeof "hello" | "string" |
- |
| typeof 42 | "number" |
- |
| typeof 123n | "bigint" |
- |
| typeof true | "boolean" |
- |
| typeof Symbol() | "symbol" |
- |
| typeof undefined | "undefined" |
- |
| typeof null | "object" |
歴史的バグ nullのチェックには、 === null を使用する
|
| typeof {} | "object" |
- |
| typeof [] | "object" |
配列もobjectとして扱われる |
| typeof function(){} | "function" |
- |
// 各プリミティブ型の typeof 結果
console.log(typeof "hello"); // "string"
console.log(typeof 42); // "number"
console.log(typeof 123n); // "bigint"
console.log(typeof true); // "boolean"
console.log(typeof Symbol()); // "symbol"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (歴史的バグ)
// nullのチェック
const value = null;
console.log(value === null); // true
プリミティブ型とオブジェクト型の違い
プリミティブ型とオブジェクト型の根本的な違いは、データの格納方法にある。
値渡しと参照渡し
プリミティブ型の変数には値そのものが直接格納される。
変数を別の変数に代入すると、値がコピーされる。
代入後にどちらかを変更しても、もう一方には影響しない。
一方、オブジェクト型の変数にはメモリ上のアドレス (参照) が格納される。
変数を別の変数に代入すると、参照がコピーされる。
2つの変数が同じオブジェクトを指すため、一方の変更がもう一方にも反映される。
// プリミティブ型: 値渡し
let a = 5;
let b = a; // 値がコピーされる
b = 10;
console.log(a); // 5 (変化しない)
console.log(b); // 10
// オブジェクト型: 参照渡し
let obj1 = { value: 5 };
let obj2 = obj1; // 参照がコピーされる
obj2.value = 10;
console.log(obj1.value); // 10 (同じオブジェクトを参照するため変化する)
console.log(obj2.value); // 10
ラッパーオブジェクト
string、number、booleanのプリミティブ型には、それぞれ対応するラッパーオブジェクト (String、Number、Boolean) が存在する。
プリミティブ値はメソッドやプロパティを持たないが、プロパティへアクセスしようとすると、JavaScriptエンジンが一時的にプリミティブ値をラッパーオブジェクトに変換し、メソッドを呼び出した後に破棄する。
このプロセスをオートボクシング (autoboxing) と呼ぶ。
通常、開発者がラッパーオブジェクトを明示的に生成する必要はない。
new String("hello") のように生成すると、typeofの結果が "object" になり、プリミティブ値と意図しない差異が生じる可能性がある。
// オートボクシングの例
// プリミティブ値に対してメソッドを呼び出すと、一時的にラッパーオブジェクトが生成される
const str = "hello";
console.log(str.toUpperCase()); // "HELLO"
console.log(str.length); // 5
// str 自体は変化していない
console.log(str); // "hello"
// ラッパーオブジェクトとプリミティブの違い
const primitiveStr = "hello";
const objectStr = new String("hello");
console.log(typeof primitiveStr); // "string"
console.log(typeof objectStr); // "object"
console.log(primitiveStr == objectStr); // true (値比較)
console.log(primitiveStr === objectStr); // false (型が異なる)
型の自動変換
JavaScriptでは、演算子やコンテキストに応じて型が自動変換される。
この動作を型強制 (type coercion) と呼ぶ。
暗黙的な型変換
演算子の種類と対象の型によって、自動的に型変換が行われる。
// + 演算子: 一方が文字列の場合、文字列連結になる
console.log("5" + 3); // "53" (3 が文字列に変換される)
console.log("5" + true); // "5true"
// - / * / / 演算子: 文字列が数値に変換される
console.log("5" - 3); // 2 ("5" が数値に変換される)
console.log("5" * "2"); // 10 (両方数値に変換される)
console.log("hello" - 2); // NaN ("hello" は数値に変換できない)
// 比較演算子
console.log(5 > "3"); // true ("3" が数値に変換される)
console.log("10" == 10); // true (緩い等価で型変換される)
console.log("10" === 10); // false (厳密等価では型変換しない)
// 論理演算子 (短絡評価)
console.log(5 || "default"); // 5 (truthy 値を返す)
console.log(0 || "default"); // "default" (0 は falsy)
console.log("hello" && 42); // 42 (両方 truthy の場合、最後の値を返す)
明示的な型変換
型変換を意図的に行う場合は、グローバル関数またはコンストラクタを使用する。
暗黙的な型変換に依存するよりも明示的に変換することにより、ソースコードの意図が明確になる。
// 文字列への変換
console.log(String(123)); // "123"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"
console.log((123).toString()); // "123"
// 数値への変換
console.log(Number("123")); // 123
console.log(Number("123.45")); // 123.45
console.log(Number("")); // 0 (空文字列は 0 に変換)
console.log(Number("hello")); // NaN
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN
// parseInt / parseFloat
console.log(parseInt("123abc")); // 123 (先頭から数値を解析)
console.log(parseFloat("3.14abc")); // 3.14
console.log(parseInt("abc123")); // NaN (先頭が数値でない場合)
// ブール値への変換
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(123)); // true
console.log(Boolean("hello")); // true
console.log(Boolean([])); // true (空配列も true)
// BigIntへの変換
console.log(BigInt(123)); // 123n
console.log(BigInt("456")); // 456n
// BigInt(12.5); // RangeError: 小数は変換できない
関連情報
- JavaScriptの基礎 - 変数宣言
- let / const / varの宣言方法、スコープ、ホイスティング
- JavaScriptの基礎 - 数値と算術演算子
- Number型の特性、算術演算子、Mathオブジェクト、BigInt
- JavaScriptの基礎 - 文字列
- テンプレートリテラル、文字列メソッド、タグ付きテンプレートリテラル
- JavaScriptの基礎 - 比較演算子と論理演算子
- === / ==の違い、短絡評価、Null合体演算子、オプショナルチェーン