typeof
と instanceof
: 値の型とは?typeof
instanceof
この章では、JavaScript がどのような種類の値を持っているかを見ていきます。
補助ツール:
===
この章では、厳密等価演算子を時々使用します。a === b
は、a
と b
が等しい場合に true
と評価されます。正確にどういう意味なのかは、§13.4.2「厳密等価性 (===
および !==
)」で説明しています。
この章では、型を値の集合とみなします。例えば、型 boolean
は集合 { false
, true
} です。
図 6 は JavaScript の型階層を示しています。この図から何を学ぶことができるでしょうか?
Object
のインスタンスを区別しています。Object
の各インスタンスもオブジェクトですが、その逆は成り立ちません。しかし、実際に遭遇するほぼすべてのオブジェクトは Object
のインスタンスです。例えば、オブジェクトリテラルで作成されたオブジェクトなどです。このトピックの詳細については、§29.7.3「すべてのオブジェクトが Object
のインスタンスではない」で説明しています。ECMAScript の仕様では、合計 8 つの型のみが認識されています。これらの型の名前は次のとおりです (私は仕様の名前ではなく、TypeScript の名前を使用しています)。
undefined
で、唯一の要素は undefined
null
で、唯一の要素は null
boolean
で、要素は false
と true
number
すべての数値の型 (例: -123
, 3.141
)bigint
すべてのビッグ整数の型 (例: -123n
)string
すべての文字列の型 (例: 'abc'
)symbol
すべてのシンボルの型 (例: Symbol('My Symbol')
)object
すべてのオブジェクトの型 (クラス Object
のすべてのインスタンスとそのサブクラスの型である Object
とは異なります)仕様では、値の間で重要な区別をしています
undefined
、null
、boolean
、number
、bigint
、string
、symbol
の型の要素です。(ここで JavaScript に影響を与えた)Java とは対照的に、プリミティブ値は二級市民ではありません。プリミティブ値とオブジェクトの違いはより微妙です。要するに
それ以外にも、プリミティブ値とオブジェクトはよく似ています。どちらもプロパティ (キーと値のエントリ) を持ち、同じ場所で使用できます。
次に、プリミティブ値とオブジェクトについて詳しく見ていきます。
プリミティブのプロパティを変更、追加、削除することはできません。
const str = 'abc';
.equal(str.length, 3);
assert.throws(
assert=> { str.length = 1 },
() /^TypeError: Cannot assign to read only property 'length'/
; )
プリミティブは値渡しされます: 変数 (パラメータを含む) には、プリミティブの内容が格納されます。プリミティブ値を変数に代入するか、関数の引数として渡すと、その内容がコピーされます。
const x = 123;
const y = x;
// `y` is the same as any other number 123
.equal(y, 123); assert
値渡しと参照渡しとの違いを観察する
プリミティブ値は不変であり、値で比較されるため (次のサブセクションを参照)、値渡しとID渡し (JavaScript のオブジェクトに使用される) との違いを観察する方法はありません。
プリミティブは値で比較されます: 2 つのプリミティブ値を比較するとき、それらの内容を比較します。
.equal(123 === 123, true);
assert.equal('abc' === 'abc', true); assert
この比較方法の何が特別なのかを知るには、読み進めて、オブジェクトがどのように比較されるかを調べてください。
オブジェクトの詳細については、§28「オブジェクト」と次の章で説明しています。ここでは、主にプリミティブ値とどのように異なるかに焦点を当てています。
まず、オブジェクトを作成する 2 つの一般的な方法を調べてみましょう
オブジェクトリテラル
const obj = {
first: 'Jane',
last: 'Doe',
; }
オブジェクトリテラルは、中括弧 {}
で始まり、中括弧で終わります。これは、2 つのプロパティを持つオブジェクトを作成します。最初のプロパティには、キー 'first'
(文字列) と値 'Jane'
があります。2 番目のプロパティには、キー 'last'
と値 'Doe'
があります。オブジェクトリテラルの詳細については、§28.3.1「オブジェクトリテラル: プロパティ」を参照してください。
配列リテラル
const fruits = ['strawberry', 'apple'];
配列リテラルは、角かっこ []
で始まり、角かっこで終わります。これは、2 つの要素 ('strawberry'
と 'apple'
) を持つ配列を作成します。配列リテラルの詳細については、§31.3.1「配列の作成、読み取り、書き込み」を参照してください。
デフォルトでは、オブジェクトのプロパティを自由に変更、追加、削除できます。
const obj = {};
.count = 2; // add a property
obj.equal(obj.count, 2);
assert
.count = 3; // change a property
obj.equal(obj.count, 3); assert
オブジェクトはID渡しされます (私の用語): 変数 (パラメータを含む) には、オブジェクトのIDが格納されます。
オブジェクトの ID は、ヒープ (JavaScript エンジンの共有メインメモリと考えてください) 上のオブジェクトの実際のデータへのポインタ (または透過的な参照) のようなものです。
オブジェクトを変数に代入するか、関数の引数として渡すと、その ID がコピーされます。各オブジェクトリテラルは、ヒープ上に新しいオブジェクトを作成し、その ID を返します。
const a = {}; // fresh empty object
// Pass the identity in `a` to `b`:
const b = a;
// Now `a` and `b` point to the same object
// (they “share” that object):
.equal(a === b, true);
assert
// Changing `a` also changes `b`:
.name = 'Tessa';
a.equal(b.name, 'Tessa'); assert
JavaScript は、ガベージコレクションを使用してメモリを自動的に管理します。
let obj = { prop: 'value' };
= {}; obj
これで、obj
の古い値 { prop: 'value' }
はガベージ (もう使用されていない) になりました。JavaScript は、(十分な空きメモリがある場合は、決して行われない可能性もありますが) ある時点で自動的にガベージコレクションを行います (メモリから削除します)。
詳細: ID渡し
「ID渡し」とは、オブジェクトのID (透過的な参照) が値渡しされることを意味します。このアプローチは、「共有渡し」とも呼ばれます。
オブジェクトはIDで比較されます (私の用語): 2 つの変数が等しいのは、同じオブジェクトIDが含まれている場合のみです。内容が同じ異なるオブジェクトを参照している場合は、等しくありません。
const obj = {}; // fresh empty object
.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same content assert
typeof
と instanceof
: 値の型とは?2 つの演算子 typeof
と instanceof
を使用すると、指定された値 x
の型を判断できます。
if (typeof x === 'string') ···
if (x instanceof Array) ···
どのように異なるのでしょうか?
typeof
は、仕様の 7 つの型を区別します (1 つの省略と 1 つの追加を除く)。instanceof
は、指定された値をどのクラスが作成したかをテストします。 経験則:
typeof
はプリミティブ値用、instanceof
はオブジェクト用
typeof
x |
typeof x |
---|---|
undefined |
'undefined' |
null |
'object' |
Boolean | 'boolean' |
Number | 'number' |
Bigint | 'bigint' |
String | 'string' |
Symbol | 'symbol' |
Function | 'function' |
その他すべてのオブジェクト | 'object' |
表 2 には、typeof
のすべての結果がリストされています。これらは、言語仕様の 7 つの型にほぼ対応しています。残念ながら、2 つの違いがあり、それらは言語の癖です。
typeof null
は 'null'
ではなく 'object'
を返します。これはバグです。残念ながら、修正することはできません。TC39 はそれを修正しようとしましたが、ウェブ上のコードが壊れすぎてしまいました。typeof
は 'object'
になるはずです (関数はオブジェクトです)。関数に別のカテゴリを導入するのは混乱を招きます。これらは typeof
を使用した例のいくつかです
> typeof undefined'undefined'
> typeof 123n'bigint'
> typeof 'abc''string'
> typeof {}'object'
練習:
typeof
に関する 2 つの練習
exercises/values/typeof_exrc.mjs
exercises/values/is_object_test.mjs
instanceof
この演算子は、値 x
がクラス C
によって作成されたかどうかという質問に答えます。
instanceof C x
たとえば
> (function() {}) instanceof Functiontrue
> ({}) instanceof Objecttrue
> [] instanceof Arraytrue
プリミティブ値は、何もインスタンスではありません
> 123 instanceof Numberfalse
> '' instanceof Stringfalse
> '' instanceof Objectfalse
練習:
instanceof
exercises/values/instanceof_exrc.mjs
JavaScript のオブジェクトの元のファクトリは、コンストラクタ関数です。これは、new
演算子を介して呼び出すと、自身の「インスタンス」を返す通常の関数です。
ES6 では、クラスが導入されました。これは、主にコンストラクタ関数の構文をより良くしたものです。
この本では、コンストラクタ関数とクラスという用語を同じ意味で使用しています。
クラスは、仕様の単一の型 object
をサブタイプに分割していると見なすことができます。これにより、仕様の制限された 7 つの型よりも多くの型が得られます。各クラスは、それによって作成されたオブジェクトの型です。
各プリミティブ型 (undefined
および null
の仕様内部の型を除く) には、関連付けられたコンストラクタ関数 (クラスと考えてください) があります。
Boolean
は、ブール値に関連付けられています。Number
は、数値に関連付けられています。String
は、文字列に関連付けられています。Symbol
は、シンボルに関連付けられています。これらの各関数は、いくつかの役割を果たします。例えば、Number
は
関数として使用して、値を数値に変換できます。
.equal(Number('123'), 123); assert
Number.prototype
は、数値のプロパティを提供します。例えば、メソッド .toString()
などです。
.equal((123).toString, Number.prototype.toString); assert
Number
は、数値用のツール関数の名前空間/コンテナオブジェクトです。例えば、次のようなものです
.equal(Number.isInteger(123), true); assert
最後に、Number
をクラスとして使用して、数値オブジェクトを作成することもできます。これらのオブジェクトは実際の数値とは異なり、避けるべきです。
.notEqual(new Number(123), 123);
assert.equal(new Number(123).valueOf(), 123); assert
プリミティブ型に関連するコンストラクタ関数は、プリミティブ値をオブジェクトに変換する正規の方法を提供するので、ラッパー型とも呼ばれます。その過程で、プリミティブ値はオブジェクトに「ラップ」されます。
const prim = true;
.equal(typeof prim, 'boolean');
assert.equal(prim instanceof Boolean, false);
assert
const wrapped = Object(prim);
.equal(typeof wrapped, 'object');
assert.equal(wrapped instanceof Boolean, true);
assert
.equal(wrapped.valueOf(), prim); // unwrap assert
ラッピングは実際にはほとんど問題になりませんが、プリミティブにプロパティを与えるために、言語仕様の内部で使用されています。
JavaScriptでは、値を他の型に変換する方法が2つあります。
プリミティブ型に関連付けられた関数は、値をその型に明示的に変換します。
> Boolean(0)false
> Number('123')123
> String(123)'123'
値をオブジェクトに変換するためにObject()
を使用することもできます。
> typeof Object(123)'object'
次の表で、この変換がどのように機能するかを詳しく説明します。
x |
Object(x) |
---|---|
undefined |
{} |
null |
{} |
boolean | new Boolean(x) |
number | new Number(x) |
bigint | BigInt のインスタンス (new はTypeError をスローします) |
string | new String(x) |
symbol | Symbol のインスタンス (new はTypeError をスローします) |
object | x |
多くの操作では、JavaScriptはオペランド/パラメータの型が一致しない場合、自動的に変換します。この種の自動変換は型強制と呼ばれます。
たとえば、乗算演算子はオペランドを数値に型強制します。
> '7' * '3'21
多くの組み込み関数も型強制を行います。たとえば、Number.parseInt()
は、解析する前にパラメータを文字列に型強制します。それは次の結果を説明します。
> Number.parseInt(123.45)123
数値123.45
は、解析される前に文字列'123.45'
に変換されます。解析は、最初の数字以外の文字の前で停止するため、結果は123
になります。
練習問題: 値のプリミティブ型への変換
exercises/values/conversion_exrc.mjs
クイズ
クイズアプリをご覧ください。