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…)を使用してください。