potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

JavaScript ECMAScriptのプリミティブ値とプリミティブデータ型について調べたこと

2021/3/10:この記事は別のブログで投稿した記事を移動したものです。

久しぶりにJavaScriptを触ったら引数の文字列判定で悩んだので、周辺情報として調べたプリミティブ値とプリミティブデータ型について記録します。

プリミティブ値とプリミティブデータ型

ECMAScript最新版1は7種類のプリミティブデータ型とそれぞれにプリミティブ値を定義します2。プリミティブデータ型はオブジェクトではありませんが、nullundefined以外は対応するラッパーオブジェクト3prototypeなどを提供します。プリミティブデータ型、プリミティブ値の例の一覧は以下の通りです。

プリミティブデータ型 プリミティブ値の例
Boolean true, false
Null null
Undefined undefined
Number 0
BigInt 0n
String ""
Symbol Symbol("")
const primitiveDataTypeNameAndValues = {
    "Boolean":[true,false],
    "Null":[null],
    "Undefined":[undefined],
    "Number":[0],
    "BigInt":[0n],
    "String":[""],
    "Symbol":[Symbol("")]};

プリミティブ値とObject.getPrototypeOf()

ECMAScript(ES2015~現時点)の仕様によりプリミティブ値はtypeofObject.getPrototypeOf()instanceofの結果が矛盾します。例えば文字列リテラル"abc"についてこれらを実行した結果は次です。

typeof "abc" // "string"
Object.getPrototypeOf("abc") // String {...}
"abc" instanceof String // "false"
"abc" instanceof Object // "false"

typeofObject.getPrototypeOf()は異なる値を返し、Object.getPrototypeOf()がStringを返すにも関わらずinstanceofではStringのインスタンスではないと判断されます。これはES2015からObject.getPrototypeOf()undefinednullを除くプリミティブ値に対してラッパーオブジェクトを返す仕様へ変更されたことによります4。次のコードですべてのプリミティブ値についてこの結果を確認できます。

const primitiveDataTypeNameAndValues = {
    "Boolean":[true,false],
    "Null":[null],
    "Undefined":[undefined],
    "Number":[0],
    "BigInt":[0n],
    "String":[""],
    "Symbol":[Symbol("")]};
console.log([
        "プリミティブデータ型",
        "プリミティブ値",
        "typeof プリミティブ値",
        "Object.getPrototypeOf(\"プリミティブ値\")"]);
for (const [primitiveDataTypeName, primitiveDataValues] of Object.entries(primitiveDataTypeNameAndValues)) {
    for (const primitiveDataValue of primitiveDataValues) {
        const isUndefinedOrNull = primitiveDataValue === undefined || primitiveDataValue === null;
        console.log([
            primitiveDataTypeName,
            primitiveDataValue,
            typeof primitiveDataValue,
            !isUndefinedOrNull ? Object.getPrototypeOf(primitiveDataValue) : null]);
    }
}

実行結果

["プリミティブデータ型", "プリミティブ値", "typeof プリミティブ値", "Object.getPrototypeOf("プリミティブ値")"]
["Boolean", true, "boolean", Boolean]
["Boolean", false, "boolean", Boolean]
["Null", null, "object", null]
["Undefined", undefined, "undefined", null]
["Number", 0, "number", Number]
["BigInt", 0n, "bigint", BigInt]
["String", "", "string", String]
["Symbol", Symbol(), "symbol", Symbol]

おまけ

文字列判定

JavaScriptの文字列にはプリミティブ値"..."new String(...)により作成するラッパーオブジェクトが存在します5。文字列判定ではどちらも考慮する必要があります。

function isString(obj) {
    return typeof obj == "string" || obj instanceof String;
}
function isPrimitiveString(obj) {
    return typeof obj == "string";
}
function isStringWrapper(obj) {
    return obj instanceof String;
}
const f = (obj) => [isString(obj), isPrimitiveString(obj), isStringWrapper(obj)]
                                   // [文字列,プリミティブ値,ラッパーオブジェクト]
console.log(f("abc"));             // [true, true, false]
console.log(f(new String("abc"))); // [true, false, true]

Object.getPrototypeOf(...).valueOf()の戻り値はプリミティブデータ型ごとに固定またはエラー

Object.getPrototypeOf()の戻り値はラッパーオブジェクトなのでvalueOf()を持ちますが、その戻り値はプリミティブ値によらずプリミティブデータ型ごとに一定です。例えばNumberでは0Booleanではfalseです。BigIntSymbolではTypeErrorとなります。

const f = (o) => console.log([o, o.constructor.name, typeof o, o.valueOf()]);
                                   // [値、コンストラクタ名、typeof、valueOf()]
f(Object.getPrototypeOf(true));    // [Boolean, "Boolean", "object", false]
f(Object.getPrototypeOf(false));   // [Boolean, "Boolean", "object", false]
f(Object.getPrototypeOf(123));     // [Number, "Number", "object", 0]
f(Object.getPrototypeOf("abc"));   // [String, "String", "object", ""]
// f(Object.getPrototypeOf(123n));          // <TypeError>
// f(Object.getPrototypeOf(Symbol("abc"))); // <TypeError>