メインコンテンツまでスキップ

ボックス化 (boxing)

多くの言語では、プリミティブは一般的にフィールドやメソッドを持ちません。プリミティブをオブジェクトのように扱うには、プリミティブをオブジェクトに変換する必要があります。プリミティブからオブジェクトへの変換をボックス化(boxing)と言います。

ts
// プリミティブ型
const str = "abc";
// ラッパーオブジェクトに入れる
const strObject = new String(str);
// オブジェクトのように扱う
strObject.length; // フィールドの参照
strObject.toUpperCase(); // メソッド呼び出し
ts
// プリミティブ型
const str = "abc";
// ラッパーオブジェクトに入れる
const strObject = new String(str);
// オブジェクトのように扱う
strObject.length; // フィールドの参照
strObject.toUpperCase(); // メソッド呼び出し

上の例は、JavaScriptでボックス化のイメージを書いたものです。実際のコードでは、プリミティブ型をStringのようなラッパーオブジェクトにわざわざ入れる必要はありません。JavaScriptには自動ボックス化という仕組みがあるからです。

自動ボックス化

JavaScriptでは、プリミティブ型の値でもフィールドを参照できたり、メソッドが呼び出せます。

ts
const str = "abc";
// オブジェクトのように扱う
str.length; // フィールドの参照
str.toUpperCase(); // メソッド呼び出し
ts
const str = "abc";
// オブジェクトのように扱う
str.length; // フィールドの参照
str.toUpperCase(); // メソッド呼び出し

プリミティブ型の値はオブジェクトではないため、このような操作ができるのは変です。ボックス化する必要があるように思えます。しかし、このようなことができるのは、JavaScriptが内部的にプリミティブ型の値をオブジェクトに変換しているからです。この暗黙の変換を自動ボックス化(auto-boxing)と呼びます。

ラッパーオブジェクト

JavaScriptの自動ボックス化で変換先となるオブジェクトをラッパーオブジェクト(wrapper object)と呼びます。プリミティブ型とラッパーオブジェクトの対応は次の表のとおりです。

プリミティブ型ラッパーオブジェクト
booleanBoolean
numberNumber
stringString
symbolSymbol
bigintBigInt

プリミティブ型のundefinednullにはラッパーオブジェクトがありません。したがって、メソッドやフィールドの参照は常にエラーが発生します。

ts
null.toString();
Object is possibly 'null'.2531Object is possibly 'null'.
undefined.toString();
Object is possibly 'undefined'.2532Object is possibly 'undefined'.
ts
null.toString();
Object is possibly 'null'.2531Object is possibly 'null'.
undefined.toString();
Object is possibly 'undefined'.2532Object is possibly 'undefined'.

MDNの読み方

JavaScriptを学ぶ過程で一度はお世話になるドキュメントがMDN Web Docsです。自動ボックス化とラッパーオブジェクトを意識すると、MDNのドキュメントが理解しやすくなります。

たとえば、数値のtoStringメソッドの説明は、MDNでは「Number.prototype.toString()」というタイトルのページに書かれています。toStringがプリミティブ型のnumberに生えているものだと思っていると、「Number.prototypeは何だろう」「数値型を調べているはずなのに、なぜNumberオブジェクトのページに書いてあるんだろう」などといった疑問を持つかもしれません。

自動ボックス化とラッパーオブジェクトを知っていると、この疑問が解消します。numberにはメソッドもフィールドもありません。メソッドなどがあるように見えるのは、自動ボックス化でnumberNumberオブジェクトに変換されるためです。したがって、toStringの説明がNumberオブジェクトのページに書いてあることが腑に落ちます。また、Number.prototypeが表す意味は「Numberオブジェクトのインスタンスに生えている」ということも理解できます。

ラッパーオブジェクトとTypeScriptの型

TypeScriptでは、ラッパーオブジェクトの型も定義されています。次のように、ラッパーオブジェクトの型を使って、型注釈を書くこともできます。ラッパーオブジェクト型の変数にプリミティブ型の値を代入するのも可能です。

ts
const bool: Boolean = false;
const num: Number = 0;
const str: String = "";
const sym: Symbol = Symbol();
const big: BigInt = 10n;
ts
const bool: Boolean = false;
const num: Number = 0;
const str: String = "";
const sym: Symbol = Symbol();
const big: BigInt = 10n;

しかし、ラッパーオブジェクト型はプリミティブ型に代入できません。

ts
const n1: Number = 0;
const n2: number = n1;
Type 'Number' is not assignable to type 'number'. 'number' is a primitive, but 'Number' is a wrapper object. Prefer using 'number' when possible.2322Type 'Number' is not assignable to type 'number'. 'number' is a primitive, but 'Number' is a wrapper object. Prefer using 'number' when possible.
ts
const n1: Number = 0;
const n2: number = n1;
Type 'Number' is not assignable to type 'number'. 'number' is a primitive, but 'Number' is a wrapper object. Prefer using 'number' when possible.2322Type 'Number' is not assignable to type 'number'. 'number' is a primitive, but 'Number' is a wrapper object. Prefer using 'number' when possible.

ラッパーオブジェクト型は演算子が使えません。

ts
const num: Number = 1;
num * 2;
The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.2362The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
ts
const num: Number = 1;
num * 2;
The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.2362The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.

ラッパーオブジェクト型は、そのインターフェースを満たしたオブジェクトであれば、プリミティブ型の値以外も代入できます。

ts
const boolLike = {
valueOf(): boolean {
return true;
},
};
const bool: Boolean = boolLike;
ts
const boolLike = {
valueOf(): boolean {
return true;
},
};
const bool: Boolean = boolLike;

プリミティブ型の代わりに、ラッパーオブジェクト型を型注釈に使う利点はありません。型注釈にはプリミティブ型を使いましょう。

ts
// ❌間違い
const num1: Number = 0;
// ✅正しい
const num2: number = 0;
ts
// ❌間違い
const num1: Number = 0;
// ✅正しい
const num2: number = 0;
学びをシェアする

・ボックス化とはプリミティブをオブジェクトに変換すること
・JavaScriptでプリミティブがオブジェクトのように扱えるのは、自動ボックス化のおかげ
・TypeScriptではラッパーオブジェクト(例:String)よりもプリミティブ型(例:string)で型注釈すべし

『サバイバルTypeScript』より

この内容をツイートする

関連情報

📄️ プリミティブ型

JavaScriptのデータ型は、プリミティブ型とオブジェクトの2つに分類されます。
  • 質問する ─ 読んでも分からなかったこと、TypeScriptで分からないこと、お気軽にGitHubまで🙂
  • 問題を報告する ─ 文章やサンプルコードなどの誤植はお知らせください。