JavaScript中基本类型包含Undefined、Null、Boolean、Number、String以及Object引用类型。
基本类型可以通过typeof来进行检测,对象类型可以通过instanceof来检测。
但这两检测方式本身存在大量的陷阱,因此需要进行兼容处理。
对于typeof,只能识别出undefined、object、boolean、number、string、function这6种数据类型,无法识别Null等细分的对象类型。
typeof本身存在的陷阱:
typeof null; 结果为"object"
typeof document.all; 在IE外的其他现代浏览器下会返回"undefined",但实际上是可用的(该方法被大量用作判断IE,因此浏览器厂商也有对应规则)。
typeof document.childNodes; 在safari下结果为"function"
typeof document.createElement('embed'); 在firefox下结果为"function"
typeof document.createElement('object'); 在firefox下结果为"function"
typeof document.createElement('applet'); 在firefox下结果为"function"
typeof window.alert; 在IE678下为"object",
IE678下有个HACK技巧:window==document 在IE678下为true(而document==window为false)。
对于typeof,在IE下判断ActiveX对象的方法时还会返回unknow的情况,例如:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <style rel="stylesheet" type="text/css"> 6 </style> 7 <script> 8 window.onload=function(){ 9 if(window.ActiveXObject){ 10 var xhr=new ActiveXObject("Msxml2.XMLHTTP"); 11 document.body.innerHTML=(typeof xhr.abort); 12 } 13 } 14 </script> 15 </head> 16 <body><div class="show">HELLO</div></body> 17 </html>
对于如上的这个IE特性,可以用来判断VBScript方法是否存在:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <style rel="stylesheet" type="text/css"> 6 </style> 7 <script type="text/VBScript"> 8 function vbf(a,b) 9 vbf = a+b 10 end function 11 </script> 12 <script type="text/javascript"> 13 window.onload=function(){ 14 // 如下在IE下为"unknow" 15 document.body.innerHTML=(typeof vbf); 16 } 17 </script> 18 </head> 19 <body><div class="show">HELLO</div></body> 20 </html>
对于instanceof,原型上存在此对象的构造器就会返回true,但有如下陷阱:
跨文档的iframe里的数组实例不是父窗口的Array的实例:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <style rel="stylesheet" type="text/css"> 6 </style> 7 <script> 8 window.onload=function(){ 9 document.body.appendChild(document.createElement("iframe")); 10 var frame=window.frames[window.frames.length-1]; 11 // IE678无法获取 12 if("Array" in frame){ 13 var fArray = frame.Array; 14 var a=new fArray(1); 15 alert(a instanceof Array); // false 16 alert(a.constructor == Array); // false 17 } 18 } 19 </script> 20 </head> 21 <body><div class="show">HELLO</div></body> 22 </html>
对于对象的constructor方法的陷阱:
在IE67下window.constructor、document.constructor等BOM与DOM对象的constructor属性是未暴露的。
在IE6789下ActiveXObject对象的constructor方法也是未暴露的。
通过如上的分析,先定义一个对象用于内建对象的字符串名称的类型映射:
1 var class2type={ 2 "[object Array]":"array", 3 "[object Boolean]":"boolean", 4 "[object Date]":"date", 5 "[object Function]":"function", 6 "[object Number]":"number", 7 "[object Object]":"object", 8 "[object RegExp]":"regexp", 9 "[object String]":"string" 10 };
然后通过Object.prototype.toString这个方法来输出对象内部对应的字符串名称来判断:
1 function type(obj){ 2 return obj == null ? String(obj) : class2type[Object.prototype.toString.call(obj)] || "object"; 3 }
对于数字的判断,首先要判断是否为数字类型(isNaN),其次还要判断是否为数字中的极值(isFinite):
1 // 只要可以转换为运算数字. 2 function isNumberic(obj){ 3 return !isNaN(parseFloat(obj)) && isFinite(obj); 4 }
在Jquery2.1中,对数字的判断更加精简,如下所示:
1 function isNumberic(obj){ 2 return obj - parseFloat(obj) >= 0; 3 }
对于window对象的判断,在JQuery 1.7.2中判断比较简单,通过检查obj.window==obj来判断,
如果使用Object.prototype.toString,因为window对象是宿主对象,非ECMA规范对象,因此返回的字符串名可能如下:
IE678 返回[object Object]
IE9、FF 返回[object Window]
Chrome 返回[object global]
Safari 返回[object DOMWindow]
再通过如上的对于IE678的 (obj===obj.document&&obj.document!==obj) 来判断IE678下的window对象,最后实现如下:
1 // 通过Object.prototype.toString判断,但IE678下通过HACK判断. 2 function isWindow(obj){ 3 return obj != null && (/Window|global/.test(Object.prototype.toString.call(obj))||(obj==obj.document&&obj.document!=obj)); 4 }
另外通常需要实现一个方法,判断一个参数是否为字面量{}创建的对象或new Object()创建的对象,
需要先排除原生对象,排除DOM对象,排除window对象,
再需要判断对象的原型是否存在isPrototypeOf方法,不存在也排除(注意捕获异常)。
综上所述,整理后的代码如下:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <style rel="stylesheet" type="text/css"> 6 </style> 7 <script type="text/javascript"> 8 var class2type={ 9 "[object Array]":"array", 10 "[object Boolean]":"boolean", 11 "[object Date]":"date", 12 "[object Function]":"function", 13 "[object Number]":"number", 14 "[object Object]":"object", 15 "[object RegExp]":"regexp", 16 "[object String]":"string" 17 }; 18 function type(obj){ 19 return obj == null ? String(obj) : class2type[Object.prototype.toString.call(obj)] || "object"; 20 } 21 function isFunction(obj){ 22 // 这样判断解决了FF下对embed、object、applet的typeof返回"function"的陷阱. 23 return type(obj) === "function"; 24 } 25 function isArray(obj){ 26 // 解决了instanceof Array不能对iframe中的Array对象正确判断的陷阱. 27 return type(obj) === "array"; 28 } 29 function isNaN(obj){ 30 return obj !== obj; 31 } 32 function isNull(obj){ 33 return obj === null; 34 } 35 function isNull2(obj){ 36 return type(obj) === "null"; 37 } 38 function isUndefined(obj){ 39 return obj === void 0; 40 } 41 function isUndefined2(obj){ 42 return type(obj) === "undefined"; 43 } 44 // 只要可以转换为运算数字. 45 function isNumberic(obj){ 46 return !isNaN(parseFloat(obj)) && isFinite(obj); 47 } 48 // Number.NEGATIVE_INFINITY-Number.NEGATIVE_INFINITY为NaN,经过判断后返回false. 49 function isNumberic2(obj){ 50 return obj - parseFloat(obj) >= 0; 51 } 52 // 通过Object.prototype.toString判断,但IE678下通过HACK判断. 53 function isWindow(obj){ 54 return obj != null && (/Window|global/.test(Object.prototype.toString.call(obj))||(obj==obj.document&&obj.document!=obj)); 55 } 56 function isPlainObject(obj){ 57 if(!obj||type(obj)!=="object"||obj.nodeType||isWindow(obj)){ 58 return; 59 } 60 try{ 61 // 存在构造函数但最近的原型对象中不存在isPrototypeOf这个Object原型特有属性。 62 if(obj.constructor&&!Object.prototype.hasOwnProperty.call(obj.constructor.prototype,"isPrototypeOf")){ 63 return false; 64 } 65 } 66 catch(e){ 67 // IE678都有可能在这里抛出异常,当obj为一些没有暴露constructor的原生对象的时候. 68 return false; 69 } 70 var key; 71 for(key in obj){} 72 // for in 循环先枚举非继承属性,再枚举继承属性. 73 // 如果对象的最后一个属性是非继承属性,那么所有属性都是非继承属性. 74 return key === undefined || Object.prototype.call(obj,key); 75 } 76 window.onload=function(){ 77 /* 进行一些测试 */ 78 var br="<br/>" 79 var nframe=document.body.appendChild(document.createElement("iframe")); 80 var frame=window.frames[window.frames.length-1]; 81 nframe.style.display="none"; 82 // IE678无法获取 83 if("Array" in frame){ 84 var fArray = frame.Array; 85 var a=new fArray(1); 86 document.body.innerHTML += "instanceof判断Array在跨框架下的结果:" + (a instanceof Array) + br; // false. 87 document.body.innerHTML += "通过原型的toString方法判断的结果:" + isArray(a) + br; // true. 88 } 89 document.body.innerHTML += "window:" + isWindow(window) + br; // true. 90 document.body.innerHTML += "new Object:" + isPlainObject({}) + br; 91 } 92 </script> 93 </head> 94 <body><div class="show">HELLO</div></body> 95 </html>