2021/3/10:この記事は別のブログで投稿した記事を移動したものです。
久しぶりにJavaScriptを触ったら引数の文字列判定で悩んだので、周辺情報として調べたプリミティブ値とプリミティブデータ型について記録します。
プリミティブ値とプリミティブデータ型
ECMAScript最新版1は7種類のプリミティブデータ型とそれぞれにプリミティブ値を定義します2。プリミティブデータ型はオブジェクトではありませんが、null
とundefined
以外は対応するラッパーオブジェクト3がprototype
などを提供します。プリミティブデータ型、プリミティブ値の例の一覧は以下の通りです。
プリミティブデータ型 | プリミティブ値の例 |
---|---|
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~現時点)の仕様によりプリミティブ値はtypeof
、Object.getPrototypeOf()
、instanceof
の結果が矛盾します。例えば文字列リテラル"abc"
についてこれらを実行した結果は次です。
typeof "abc" // "string" Object.getPrototypeOf("abc") // String {...} "abc" instanceof String // "false" "abc" instanceof Object // "false"
typeof
とObject.getPrototypeOf()
は異なる値を返し、Object.getPrototypeOf()
がStringを返すにも関わらずinstanceof
ではStringのインスタンスではないと判断されます。これはES2015からObject.getPrototypeOf()
がundefined
とnull
を除くプリミティブ値に対してラッパーオブジェクトを返す仕様へ変更されたことによります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
では0
、Boolean
ではfalse
です。BigInt
とSymbol
では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>