instanceof演算子を使用すると、オブジェクトが特定のクラスに属するかどうかを確認できます。継承も考慮されます。
このようなチェックは多くの場合に必要になる場合があります。たとえば、引数をその型に応じて異なる方法で処理する、ポリモーフィックな関数の構築に使用できます。
instanceof演算子
構文は次のとおりです。
obj instanceof Class
objがClassまたはそこから継承するクラスに属する場合はtrueを返します。
たとえば
class Rabbit {}
let rabbit = new Rabbit();
// is it an object of Rabbit class?
alert( rabbit instanceof Rabbit ); // true
コンストラクタ関数でも機能します
// instead of class
function Rabbit() {}
alert( new Rabbit() instanceof Rabbit ); // true
…そしてArrayのような組み込みクラスでも機能します
let arr = [1, 2, 3];
alert( arr instanceof Array ); // true
alert( arr instanceof Object ); // true
arrはObjectクラスにも属することに注意してください。これは、ArrayがプロトタイプチェーンでObjectから継承するためです。
通常、instanceofはチェックのためにプロトタイプチェーンを調べます。静的メソッドSymbol.hasInstanceでカスタムロジックを設定することもできます。
obj instanceof Classのアルゴリズムは、おおよそ次のとおりです。
-
静的メソッド
Symbol.hasInstanceがある場合は、それを呼び出すだけです:Class[Symbol.hasInstance](obj)。trueまたはfalseを返す必要があり、これで完了です。このようにして、instanceofの動作をカスタマイズできます。たとえば
// setup instanceOf check that assumes that // anything with canEat property is an animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; } } let obj = { canEat: true }; alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called -
ほとんどのクラスには
Symbol.hasInstanceがありません。その場合、標準のロジックが使用されます。obj instanceOf Classは、Class.prototypeがobjのプロトタイプチェーンのプロトタイプの1つと等しいかどうかをチェックします。言い換えれば、次々に比較します。
obj.__proto__ === Class.prototype? obj.__proto__.__proto__ === Class.prototype? obj.__proto__.__proto__.__proto__ === Class.prototype? ... // if any answer is true, return true // otherwise, if we reached the end of the chain, return false上記の例では
rabbit.__proto__ === Rabbit.prototypeなので、すぐに答えが得られます。継承の場合、2番目のステップで一致が見つかります。
class Animal {} class Rabbit extends Animal {} let rabbit = new Rabbit(); alert(rabbit instanceof Animal); // true // rabbit.__proto__ === Animal.prototype (no match) // rabbit.__proto__.__proto__ === Animal.prototype (match!)
これは、rabbit instanceof AnimalがAnimal.prototypeと比較する様子を示した図です。
ちなみに、objA.isPrototypeOf(objB)というメソッドもあり、objAがobjBのプロトタイプチェーンのどこかに存在する場合はtrueを返します。したがって、obj instanceof ClassのテストはClass.prototype.isPrototypeOf(obj)と表現し直すことができます。
面白いことに、Classコンストラクタ自体はチェックに参加しません!プロトタイプチェーンとClass.prototypeだけが重要です。
これは、オブジェクト作成後にprototypeプロパティが変更された場合に、興味深い結果につながる可能性があります。
ここでは
function Rabbit() {}
let rabbit = new Rabbit();
// changed the prototype
Rabbit.prototype = {};
// ...not a rabbit any more!
alert( rabbit instanceof Rabbit ); // false
ボーナス:型のObject.prototype.toString
プレーンオブジェクトは[object Object]として文字列に変換されることを既に知っています。
let obj = {};
alert(obj); // [object Object]
alert(obj.toString()); // the same
これはそれらのtoStringの実装です。しかし、toStringを実際にはそれよりもはるかに強力にする隠れた機能があります。これを拡張typeofとinstanceofの代替として使用できます。
奇妙に聞こえますか?確かに。解き明かしましょう。
仕様によると、組み込みのtoStringはオブジェクトから抽出して他の値のコンテキストで実行できます。その結果は、その値によって異なります。
- 数値の場合、
[object Number]になります。 - ブール値の場合、
[object Boolean]になります。 nullの場合:[object Null]undefinedの場合:[object Undefined]- 配列の場合:
[object Array] - …など(カスタマイズ可能)。
実演しましょう。
// copy toString method into a variable for convenience
let objectToString = Object.prototype.toString;
// what type is this?
let arr = [];
alert( objectToString.call(arr) ); // [object Array]
ここでは、デコレータと転送、call/applyの章で説明されているように、callを使用して、this=arrのコンテキストで関数objectToStringを実行しました。
内部的に、toStringアルゴリズムはthisを調べ、対応する結果を返します。さらに例を挙げると
let s = Object.prototype.toString;
alert( s.call(123) ); // [object Number]
alert( s.call(null) ); // [object Null]
alert( s.call(alert) ); // [object Function]
Symbol.toStringTag
Object toStringの動作は、特別なオブジェクトプロパティSymbol.toStringTagを使用してカスタマイズできます。
たとえば
let user = {
[Symbol.toStringTag]: "User"
};
alert( {}.toString.call(user) ); // [object User]
ほとんどの環境固有のオブジェクトには、このようなプロパティがあります。ブラウザ固有の例をいくつか示します。
// toStringTag for the environment-specific object and class:
alert( window[Symbol.toStringTag]); // Window
alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest
alert( {}.toString.call(window) ); // [object Window]
alert( {}.toString.call(new XMLHttpRequest()) ); // [object XMLHttpRequest]
ご覧のとおり、結果はまさにSymbol.toStringTag(存在する場合)であり、[object ...]でラップされています。
最後に、プリミティブデータ型だけでなく、組み込みオブジェクトにも機能し、カスタマイズすることもできる「強化されたtypeof」ができました。
単に確認するのではなく、型を文字列として取得したい場合は、組み込みオブジェクトに対してinstanceofの代わりに{}.toString.callを使用できます。
要約
私たちが知っている型チェックの方法を要約しましょう。
| 有効な対象 | 戻り値 | |
|---|---|---|
typeof |
プリミティブ型 | 文字列 |
{}.toString |
プリミティブ型、組み込みオブジェクト、Symbol.toStringTagを持つオブジェクト |
文字列 |
instanceof |
オブジェクト | true/false |
ご覧のとおり、{}.toStringは技術的には「より高度な」typeofです。
そして、instanceof演算子は、クラス階層を操作し、継承を考慮してクラスを確認したい場合に、真価を発揮します。
コメント
<code>タグを使用し、数行の場合は<pre>タグで囲み、10行を超える場合はサンドボックス(plnkr、jsbin、codepen…)を使用してください。