typeof と instanceof: 値の型とは?typeofinstanceofこの章では、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 で、唯一の要素は undefinednull で、唯一の要素は nullboolean で、要素は false と truenumber すべての数値の型 (例: -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';
assert.equal(str.length, 3);
assert.throws(
() => { 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
assert.equal(y, 123); 値渡しと参照渡しとの違いを観察する
プリミティブ値は不変であり、値で比較されるため (次のサブセクションを参照)、値渡しとID渡し (JavaScript のオブジェクトに使用される) との違いを観察する方法はありません。
プリミティブは値で比較されます: 2 つのプリミティブ値を比較するとき、それらの内容を比較します。
assert.equal(123 === 123, true);
assert.equal('abc' === 'abc', true);この比較方法の何が特別なのかを知るには、読み進めて、オブジェクトがどのように比較されるかを調べてください。
オブジェクトの詳細については、§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 = {};
obj.count = 2; // add a property
assert.equal(obj.count, 2);
obj.count = 3; // change a property
assert.equal(obj.count, 3);オブジェクトは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):
assert.equal(a === b, true);
// Changing `a` also changes `b`:
a.name = 'Tessa';
assert.equal(b.name, 'Tessa');JavaScript は、ガベージコレクションを使用してメモリを自動的に管理します。
let obj = { prop: 'value' };
obj = {};これで、obj の古い値 { prop: 'value' } はガベージ (もう使用されていない) になりました。JavaScript は、(十分な空きメモリがある場合は、決して行われない可能性もありますが) ある時点で自動的にガベージコレクションを行います (メモリから削除します)。
詳細: ID渡し
「ID渡し」とは、オブジェクトのID (透過的な参照) が値渡しされることを意味します。このアプローチは、「共有渡し」とも呼ばれます。
オブジェクトはIDで比較されます (私の用語): 2 つの変数が等しいのは、同じオブジェクトIDが含まれている場合のみです。内容が同じ異なるオブジェクトを参照している場合は、等しくありません。
const obj = {}; // fresh empty object
assert.equal(obj === obj, true); // same identity
assert.equal({} === {}, false); // different identities, same contenttypeof と instanceof: 値の型とは?2 つの演算子 typeof と instanceof を使用すると、指定された値 x の型を判断できます。
if (typeof x === 'string') ···
if (x instanceof Array) ···どのように異なるのでしょうか?
typeof は、仕様の 7 つの型を区別します (1 つの省略と 1 つの追加を除く)。instanceof は、指定された値をどのクラスが作成したかをテストします。 経験則:
typeof はプリミティブ値用、instanceof はオブジェクト用
typeofx |
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.mjsinstanceofこの演算子は、値 x がクラス C によって作成されたかどうかという質問に答えます。
x instanceof Cたとえば
> (function() {}) instanceof Function
true
> ({}) instanceof Object
true
> [] instanceof Array
trueプリミティブ値は、何もインスタンスではありません
> 123 instanceof Number
false
> '' instanceof String
false
> '' instanceof Object
false 練習:
instanceof
exercises/values/instanceof_exrc.mjs
JavaScript のオブジェクトの元のファクトリは、コンストラクタ関数です。これは、new 演算子を介して呼び出すと、自身の「インスタンス」を返す通常の関数です。
ES6 では、クラスが導入されました。これは、主にコンストラクタ関数の構文をより良くしたものです。
この本では、コンストラクタ関数とクラスという用語を同じ意味で使用しています。
クラスは、仕様の単一の型 object をサブタイプに分割していると見なすことができます。これにより、仕様の制限された 7 つの型よりも多くの型が得られます。各クラスは、それによって作成されたオブジェクトの型です。
各プリミティブ型 (undefined および null の仕様内部の型を除く) には、関連付けられたコンストラクタ関数 (クラスと考えてください) があります。
Boolean は、ブール値に関連付けられています。Number は、数値に関連付けられています。String は、文字列に関連付けられています。Symbol は、シンボルに関連付けられています。これらの各関数は、いくつかの役割を果たします。例えば、Number は
関数として使用して、値を数値に変換できます。
assert.equal(Number('123'), 123);Number.prototype は、数値のプロパティを提供します。例えば、メソッド .toString() などです。
assert.equal((123).toString, Number.prototype.toString);Number は、数値用のツール関数の名前空間/コンテナオブジェクトです。例えば、次のようなものです
assert.equal(Number.isInteger(123), true);最後に、Number をクラスとして使用して、数値オブジェクトを作成することもできます。これらのオブジェクトは実際の数値とは異なり、避けるべきです。
assert.notEqual(new Number(123), 123);
assert.equal(new Number(123).valueOf(), 123);プリミティブ型に関連するコンストラクタ関数は、プリミティブ値をオブジェクトに変換する正規の方法を提供するので、ラッパー型とも呼ばれます。その過程で、プリミティブ値はオブジェクトに「ラップ」されます。
const prim = true;
assert.equal(typeof prim, 'boolean');
assert.equal(prim instanceof Boolean, false);
const wrapped = Object(prim);
assert.equal(typeof wrapped, 'object');
assert.equal(wrapped instanceof Boolean, true);
assert.equal(wrapped.valueOf(), prim); // unwrapラッピングは実際にはほとんど問題になりませんが、プリミティブにプロパティを与えるために、言語仕様の内部で使用されています。
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
クイズ
クイズアプリをご覧ください。