type detection
2011-07-10追記 モダンブラウザでは、 typeof /^a/ は "function" ではなく "object" に修正されています。2009年10月(このエントリの初出)時点で /^a/("a") と記述可能な場合がありましたが、現在はエラーになります。
2010-01-21追記 http://d.hatena.ne.jp/uupaa/20100120/1263998056
2009-10-18追記
uu.type(new String('hoge'))とか渡した時4が帰ってきたりするので、プリミティブラッパー(ってJSで言うのか?)の判定を_TYPEに入れたほうがいいのかもしれませんね。
by tera
コメント欄でのご指摘を受け、以下のコードを追記しています。
"[object Boolean]": 0x40, // uu.type(new Boolean(false)) 2009-10-18追記 "[object Number]": 0x80, // uu.type(new Number(0)) 2009-10-18追記 "[object String]": 0x100, // uu.type(new String('')) 2009-10-18追記
tera さん フォローありがとうございます。
本文
js は型を判断する簡単な方法が用意されていません。
各ブラウザが typeof と Object.prototype.toString.call で返す値を調べ、そこから型を判断する方法を模索してみます。
各ブラウザの戻り値を調べてみる
<script> window.onload = function() { show("undef", void 0); show("null", null); show("bool", false); show("num", 1); show("str", "a"); show("regexp", /^a/); show("ary", []); show("func", show); show("date", new Date); show("node", document.body); show("nodelist", document.getElementsByTagName("body")); show("arguments", arguments); } function show(title, mix) { alert(title + "=" + typeof mix + "," + Object.prototype.toString.call(mix)); } </script>
結果
以下の気になるポイントに色をつけておきました。
- RegExp
- Firefox2(function) → Firefox3(object)
- Google Chrome3(object) → Google Chrome4(function)
- NodeList
- Safari3+(function)
- typeof mix で正しく判断できるのは、undefined, boolean, number, string のみ
- typeof mix === "function" は function, RegExp, NodeList で true になる可能性がある
- Object.prototype.toString.call(undefined) や call(null) は call(window) と同じ結果になっている
- ECMAScript で明文化されてるか分からないけど、現状そうなってるので、そういう仕様なんだと思う。
Chrome 3+ | typeof | Object.prototype.toString.call |
---|---|---|
undefined | "undefined" | "[object builtins]" |
null | "object" | "[object builtins]" |
false | "boolean" | "[object Boolean]" |
1 | "number" | "[object Number]" |
"a" | "string" | "[object String]" |
/^a/ (Chrome 3) | "object" | "[object RegExp]" |
/^a/ (Chrome 4) | "function" | "[object RegExp]" |
/^a/ (Chrome 14) | "object" | "[object RegExp]" |
[] | "object" | "[object Array]" |
show | "function" | "[object Function]" |
new Date | "object" | "[object Date]" |
document.body | "object" | "[object HTMLBodyElement]" |
document.getElementsByTagName("body") | "object" | "[object NodeList]" |
arguments | "object" | "[object Object]" |
Safari3.1+ | typeof | Object.prototype.toString.call |
---|---|---|
undefined | "undefined" | "[object DOMWindow]" |
null | "object" | "[object DOMWindow]" |
false | "boolean" | "[object Boolean]" |
1 | "number" | "[object Number]" |
"a" | "string" | "[object String]" |
/^a/ (Safari 3.1) | "function" | "[object RegExp]" |
/^a/ (Safari 5.1) | "object" | "[object RegExp]" |
[] | "object" | "[object Array]" |
show | "function" | "[object Function]" |
new Date | "object" | "[object Date]" |
document.body | "object" | "[object HTMLBodyElement]" |
document.getElementsByTagName("body") | "function" | "[object NodeList]" |
arguments | "object" | "[object Arguments]" |
Firefox2+ | typeof | Object.prototype.toString.call |
---|---|---|
undefined | "undefined" | "[object Window]" |
null | "object" | "[object Window]" |
false | "boolean" | "[object Boolean]" |
1 | "number" | "[object Number]" |
"a" | "string" | "[object String]" |
/^a/ (Firefox 2) | "function" | "[object RegExp]" |
/^a/ (Firefox 3) | "object" | "[object RegExp]" |
/^a/ (Firefox 3.5) | "object" | "[object RegExp]" |
/^a/ (Firefox 5) | "object" | "[object RegExp]" |
[] | "object" | "[object Array]" |
show | "function" | "[object Function]" |
new Date | "object" | "[object Date]" |
document.body | "object" | "[object HTMLBodyElement]" |
document.getElementsByTagName("body") | "object" | "[object HTMLCollection]" |
arguments | "object" | "[object Object]" |
IE6+ | typeof | Object.prototype.toString.call |
---|---|---|
undefined | "undefined" | "[object Object]" |
null | "object" | "[object Object]" |
false | "boolean" | "[object Boolean]" |
1 | "number" | "[object Number]" |
"a" | "string" | "[object String]" |
/^a/ | "object" | "[object RegExp]" |
/^a/ (IE9) | "object" | "[object RegExp]" |
[] | "object" | "[object Array]" |
show | "function" | "[object Function]" |
new Date | "object" | "[object Date]" |
document.body | "object" | "[object Object]" |
document.getElementsByTagName("body") | "object" | "[object Object]" |
arguments | "object" | "[object Object]" |
Opera9.5+ | typeof | Object.prototype.toString.call |
---|---|---|
undefined | "undefined" | "[object Window]" |
null | "object" | "[object Window]" |
false | "boolean" | "[object Boolean]" |
1 | "number" | "[object Number]" |
"a" | "string" | "[object String]" |
/^a/ (Opera 9.5) | "object" | "[object RegExp]" |
/^a/ (Opera 11.50) | "object" | "[object RegExp]" |
[] | "object" | "[object Array]" |
show | "function" | "[object Function]" |
new Date | "object" | "[object Date]" |
document.body | "object" | "[object HTMLBodyElement]" |
document.getElementsByTagName("body") | "object" | "[object NodeList]" |
arguments | "object" | "[object Object]" |
型を判断する順番
順番も重要になります。
型が不明な変数 mix の型を判断するには、
- typeof mix で undefined, Boolean, Number, String を検出
- Object.prototype.toString.call(mix) で RegExp, Array, Function, Date を検出
- !mix で null を検出
- "length" in mix で 擬似配列(NodeList や arguments) を検出
- これ以外は Object 型
とやればよさそうです。
typeof mix の次に Object.prototype.toString.call(mix) するのは call(mix) が重いからです。
コード化してみる
Google Code Archive - Long-term storage for Google Code Project Hosting.
var uu = {}; // library namespace (function() { var _TYPE = { "undefined": 0x20, "boolean": 0x40, "number": 0x80, "string": 0x100, "[object Boolean]": 0x40, // uu.type(new Boolean(false)) 2009-10-18追記 "[object Number]": 0x80, // uu.type(new Number(0)) 2009-10-18追記 "[object String]": 0x100, // uu.type(new String('')) 2009-10-18追記 "[object RegExp]": 0x200, "[object Array]": 0x400, "[object Function]": 0x800, "[object Date]": 0x1000 }, _TYPE_DETECTOR = Object.prototype.toString;
Google Code Archive - Long-term storage for Google Code Project Hosting.
uu = { // --- type --- type: uutype, // [1] uu.type("str") -> 0x100(uu.STR) // [2] uu.type("str", uu.STR | uu.NUM) -> true HASH: 1, // Object(Hash) NODE: 2, // Node FAKE: 4, // FakeArray, "length" in mix(NodeList, arguments) RGBA: 8, // { r, g, b, a } NULL: 0x10, // null UNDEF: 0x20, // undefined BOOL: 0x40, // Boolean NUM: 0x80, // Number STR: 0x100, // String REGEXP: 0x200, // RegExp ARRAY: 0x400, // Array FUNC: 0x800, // Function DATE: 0x1000 // Date };
Google Code Archive - Long-term storage for Google Code Project Hosting.
// --- type --- // uu.type - type detection // [1] uu.type("str") -> 0x100(uu.STR) // [2] uu.type("str", uu.STR | uu.NUM) -> true function uutype(mix, // @param Mix: match) { // @param Number(= 0): match types // @return Boolean/Number: true = match, // false = unmatch // number is matched bits var rv = _TYPE[typeof mix] || _TYPE[_TYPE_DETECTOR.call(mix)] || (!mix ? 16 : mix.nodeType ? 2 : "length" in mix ? 4 : ("r" in mix && "g" in mix && "b" in mix && "a" in mix) ? 8 : 1); // 16: NULL, 2: NODE, 4: FAKE, 8: RGBA, 1: HASH return match ? !!(match & rv) : rv; } })();
定数ではなく 16 や 4 などのマジックナンバー(即値)を使ってるのは速度的な理由から。
三項演算子も多用してたりと、ちょっと行儀が悪いコード片です。
まるで
バッドノウハウの塊ですね。将来大丈夫かこれ…
このページみせれば誰でもメンテできるだろうから大丈夫だね
他のライブラリってどうしてるの?
「この考え方であってんのかなー」と不安になったので有名どころを、ざーっと見てみました。
YUI 3.0.0
/* Copyright (c) 2009, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt version: 3.0.0 build: 1549 */ var L = Y.Lang, ARRAY = 'array', BOOLEAN = 'boolean', DATE = 'date', ERROR = 'error', FUNCTION = 'function', NUMBER = 'number', NULL = 'null', OBJECT = 'object', REGEX = 'regexp', STRING = 'string', TOSTRING = Object.prototype.toString, UNDEFINED = 'undefined', TYPES = { 'undefined' : UNDEFINED, 'number' : NUMBER, 'boolean' : BOOLEAN, 'string' : STRING, '[object Function]' : FUNCTION, '[object RegExp]' : REGEX, '[object Array]' : ARRAY, '[object Date]' : DATE, '[object Error]' : ERROR }, /** * Returns a string representing the type of the item passed in. * @method type * @param o the item to test * @return {string} the detected type */ L.type = function (o) { return TYPES[typeof o] || TYPES[TOSTRING.call(o)] || (o ? OBJECT : NULL); };
(・∀・)人(・∀・)ナカーマ発見。
Object.prototype.toString(o) で Error オブジェクトも取れるんですね。
# Errorを検出しても、ちょっと使い道がわからないので、実装は控えます。
おまけ
undefined かどうか調べる方法
- if (typeof mix === "undefined") {}
- if (mix === undefined) {}
- if (mix === void 0) {}
一番下がお勧めです。
Infinity や NaN は "number", "[object Number]" になります。
- show("Infinity", 1 / 0);
- show("NaN", 0 / 0);
ついったー
速度的なものも含めて色々と調べてたら、記事にするまで2日もかかってしまいました。
レスの機会を見事に逃したので、ここで感謝を
os0x さん、いつもアドバイスありがとうございます。
Safari の typeof NodeList === "function" は不具合っぽいですね。