potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

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>