JavaScript | ラッパーオブジェクトとプリミティブ型

javascrpit JavaScript
スポンサーリンク

概要

  • JavaScript の値は大きく プリミティブ(原始)型オブジェクト型 に分かれます。主なプリミティブ:Number, BigInt, String, Boolean, undefined, null, Symbol
  • Number, String, Boolean には ラッパーオブジェクトnew Number(...) 等)が存在する。プリミティブには通常プロパティやメソッドが無いが、メソッド呼び出し時に JS エンジンが一時的なオブジェクト(ラッパー)を作って処理する(=オートボクシング / autoboxing)。
  • undefinednull にはラッパーオブジェクトは無く、メソッドやプロパティアクセスは TypeError になる。

仕組み(オートボクシング) — どう動くか

例:

let s = "hello";
console.log(typeof s);          // "string" (プリミティブ)
console.log(s.toUpperCase());   // "HELLO"
JavaScript

toUpperCase() を呼ぶとき、エンジンは内部で一時的に new String(s) のようなオブジェクトを作り、そのオブジェクトのメソッドを呼び終えると破棄します。つまり見かけ上はプリミティブでもメソッドが使えるが、恒久的にオブジェクト化されるわけではありません。

代表的な具体例と結果(重要)

1) 数値リテラルでメソッドを呼ぶときの小テク

(1).toString(2)   // "1"
1..toString(2)    // "1"  ← 1. は小数点と解釈され、次の . がプロパティアクセス
// 1.toString() は構文エラーになる(1. がないので)
JavaScript

2) 一時オブジェクトにプロパティを付けても残らない

let str = "hi";
str.foo = 123;
console.log(str.foo); // undefined
JavaScript

理由:str.foo = 123 時に一時的な String オブジェクトが作られるが、代入後すぐ破棄されるため永続化しない。

3) new を使うとオブジェクトになる(落とし穴)

let p = 0;
let o = new Number(0);

console.log(typeof p);   // "number"
console.log(typeof o);   // "object"
console.log(p === o);    // false   (型が違う)
console.log(p == o);     // true    (== は型変換して値が等しい)
JavaScript

4) Boolean の罠(典型)

let bPrim = false;
let bObj  = new Boolean(false);

if (bPrim) console.log("prim true"); else console.log("prim false");
// -> "prim false"

if (bObj) console.log("obj true"); else console.log("obj false");
// -> "obj true"   ← オブジェクトは truthy
JavaScript

new Boolean(false) は中身は false だがオブジェクトなので if などの条件判定では 常に truthy に見えます。バグになりやすいです。

5) undefined/null はメソッドを持たない

let u = undefined;
u.toString(); // TypeError: Cannot read properties of undefined
JavaScript

6) BigInt, Symbol と new

  • BigIntSymbolコンストラクタではないため new BigInt(...) / new Symbol(...) は使えません(TypeError)。BigInt(123)Symbol("x") はプリミティブを返します。

比較・等価性の注意点

  • === は型と値の厳密比較。ラッパーオブジェクトは typeof"object" なので、プリミティブと === で等しくなりません。
  • == は型変換(抽象的等価性)を行うため、new Number(1) == 1true になることがある(オブジェクトがプリミティブに変換されるため)。
  • つまり new Number(0) === 0 // false だが new Number(0) == 0 // true になるケースがあり、混乱しやすい。普段は === を推奨。

実務での推奨(ベストプラクティス)

  1. new Number, new String, new Boolean を使わない。
    ほとんどのケースで不要・有害です。プリミティブ(42, "abc", true)を使うか、型変換が必要なら Number(x), String(x), Boolean(x) を使ってください(new を付けない)。
  2. 真偽判定は Boolean(x) または !!x を使う。
    new Boolean(x) はオブジェクトで truthy なので誤りの元。
  3. 型チェックは typeof / Array.isArray / instanceof を使い分ける(オブジェクト化されると typeof が変わる点に注意)。
  4. メソッドアクセスは気にせずプリミティブのままでOK。 JS が内部で一時オブジェクトを作ってくれるので "abc".toUpperCase() のように普通に書いて構いません。
  5. 数値リテラルのメソッド呼び出しは (1).toString()1..toString() のように書く(構文エラー回避)。
  6. null/undefined に対してプロパティやメソッド呼び出しをしない(事前チェックをする)。

よくある落とし穴(まとめ)

  • new Boolean(false) を if に入れると常に真になる → バグ
  • プリミティブにプロパティを設定しても残らない → 期待しないこと
  • typeof"object" になって型判定が混乱する(ラッパーを作らないことで回避)
  • == の自動型変換で思わぬ真偽になる → === を使う

追加の小ネタ・補足

  • 値を文字列化したいときは String(x)、数に変換したいときは Number(x)parseIntparseFloat を使う。Boolean(x) で真偽に変換、または !!x
  • Object(value) を使うと明示的にラッパーオブジェクトを作れますが(Object(3) -> new Number(3) 相当)、普通は不要です。
  • NaN の比較は注意:NaN !== NaN。等価判定には Number.isNaN()Object.is() を使う。

まとめ

  • 「プリミティブ値にメソッドを呼べる」のは JS が必要なときだけ一時オブジェクトを作るから(オートボクシング)。
  • new Number/String/Boolean作らない。混乱とバグの元。
  • undefinednull はラッパーを持たない → それらに対するアクセスはエラーになる。
  • 実務ではプリミティブを使い、明示的変換は Number(), String(), Boolean() を使う。
タイトルとURLをコピーしました