書いた後で気づいたのですが、こちらの方が書いた記事のほうが
完成度が何倍も高いので是非こちらをご一読ください。
背景
Typescriptを学び始めて、引数の型を判別する関数を書いていたある日、
ふとある疑問が浮かびました。
**Array.isArray(args)**ってメソッドは何のためにあるんだ
Array.isArray() - Javascript MDN
instanceofとかtypepfで判別するのはだめなの???
もしかしたら同じような疑問を持つ方が今後いらっしゃるかもしれない
&何かの助けになればと思い、記事として残しておきます。
typeofの性質
typeof
console.log(typeof 42);
// expected output: "number"
console.log(typeof 'blubber');
// expected output: "string"
typeofの後に続く変数(MDNではoperandと記載)の型を判定してくれます。
このtypeofを用いた判定、一見便利そうに見えて実は
・ プリミティブ型(StringとかNumberとか)
・ Function
・ Object
・ Symbol
のいずれか(一応undefinedも)のみを返します。
偉大な先人様のわかりやすい記事↓
プリミティブ型とオブジェクト型
console.log(typeof ['a', 'b', 'c'] === 'object');
// true
console.log(typeof "test" === 'object');
// false typeof "test" → String (プリミティブ型)
//nullはtrue, typeof NaNはnumber, typeof undefinedはundefinedを返す
console.log(typeof null === 'object');
// true
console.log(typeof NaN)
//number
console.log(typeof undefined)
//undefined
console.log(['a', 'b', 'c'] instanceof Array);
//true
console.log(['a', 'b', 'c'] instanceof Object);
// javascriptにおいてすべてのオブジェクトはObjectを継承しているのでtrue
ArrayはObject型なのでtypeofを使っても'object'として判定されてしまいますね。
そこで、別の手段として
Object.prototype.toString.call()やArg.constructorを使って型を判定することができます。
console.log(Object.prototype.toString.call('test'));
// [object String]
console.log(Object.prototype.toString.call(1));
// [object Number]
console.log({}.toString.call([1, 2, 3]));
// [object Array]
//引数が関数かどうかを判定
const isFunc = (value: unknown):boolean => {
return value && {}.toString.call(value) === "[object Function]"
}
console.log([1, 2, 3].constructor === Array);
//true
console.log("test".constructor === String);
// true
これで判定できるんだったらArray.isArray()無くてもいいのか.....?という結論になりかけたんですが、こちらに答えが書いてありました(英語サイトです。)
要約すると(拙訳すいません。御指摘ください。)
-
Object.prototype.toString.call(args) === "[object Array]"で判定するのはいい方法だけど、Object.prototype.toStringとかFunction.prototype.callの挙動が変更になったとき影響を受ける恐れがあるよ(そんなことあるんかいな...)
(but that relies on Object.prototype.toString and Function.prototype.call not being changed (probably a good assumption but still fragile).) -
instanceof Arrayとか、A.constructor === Arrayは、複数windowをまたぐときにとか、スコープが違う時には動作が保証されないよ。
(What about arrays in different windows? The shared-mutation hazard of having arrays in two coordinating windows be instances of the same Array constructor, sharing the same Array.prototype, is enormous when either page augments Array.prototype (not to mention the security problems when one page is malicious!), so Array and Array.prototype in each window must be different. Therefore, by the semantics of instanceof, o instanceof Array works correctly only if o is an array created by that page's original Array constructor (or, equivalently, by use of an array literal in that page))
(o.constructor === Array is one, with the same problem as an instanceof check.)
その点、Array.isArray()はiframesで動作するので複数windowをまたいでも正常に動作するようなので安心ですね。
結論
JavascriptにおいてArray型の判別はArray.isArray()が便利
ただし、Array.isArray()はES5の構文なので、polyfillを考えないといけない場合はおとなしくObject.prototype.toString.call()での判別でいいと思います。
以下MDNにあったpolyfill例
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
この他にもnew String等を使った場合の型など、javascriptの型システムは結構落とし穴がなにかと多い印象です....
稚拙な記事でしたが、どなたかの学びのきっかけになれば幸いです。