4.4 数据类型检测
JavaScript是弱类型语言,对类型没有严格限制,但是在程序中经常需要检测数据类型。对于如何检测类型,JavaScript提供了多种方法,下面将重点介绍两种。
4.4.1 使用typeof
typeof运算符专门用来测试值的类型,特别对于原始值比较有效,但是对于对象或数组类型数据,返回的值都是字符串"object",显然没有很大参考价值。
【示例1】下面代码显示使用typeof检测数据类型的方法。
alert(typeof 1); //返回字符串"number" alert(typeof"a"); //返回字符串"string" alert(typeof true); //返回字符串"boolean" alert(typeof{}); //返回字符串"object" alert(typeof[]); //返回字符串"object" alert(typeof function(){}); //返回字符串"function" alert(typeof undefined); //返回字符串"undefined" alert(typeof null); //返回字符串"object" alert(typeof NaN); //返回字符串"number"
【示例2】由于null值返回类型为object,用户可以定义一个检测简单数据类型的一般方法。
function type(o){ //返回值类型数据的类型字符串 return (o === null) ? "null" : (typeof o); //如果是null值,则返回字符串"null",否则返回(typeof o)表达式的值 }
上面代码避开因为null值影响基本数据的类型检测。
4.4.2 使用constructor
对于对象、数组等复杂数据,可以使用Object对象的constructor属性进行检测。constructor表示构造器,该属性值引用的是构造当前对象的函数。
【示例1】下面代码可以检测对象直接量和数组直接量的类型。
var o = {}; var a = []; alert(o.constructor==Object); //返回true alert(a.constructor==Array); //返回true
通过上面方法,可以准确判断复杂数据是对象,还是数组。如果结合typeof运算符和constructor属性,用户基本能够完成数据类型的检测,如表4-4所示列举了不同类型数据的检测结果。测试代码如下:
表4-4 数据类型检测
var value=1; //输入不同类型的值(第一列) alert(typeof value); //返回typeof运算符返回的字符串(第二列) alert(value.constructor); //返回constructor属性返回的对象(第三列)
【示例2】使用constructor属性可以检测绝大部分数据的类型,但对于undefined和null特殊值,就不能够使用constructor属性,否则会抛出异常。这时可以先把值转换为布尔值,如果为true,则说明是存在值的,然后再调用constructor属性。
var value = undefined; alert(typeof value); //返回字符串"undefined" alert(value&&value.constructor); //返回undefined var value = null; alert(typeof value); //返回字符串"object" alert(value&&value.constructor); //返回null
另外,对于数值直接量也不能够直接使用constructor属性,下面代码将会提示语法错误:
alert(10.constructor);
但是如果加上一个小括号,则可以检测:
alert((10).constructor);
这是因为小括号运算符能够把数值转换为对象。
4.4.3 案例:在框架窗口检测数组类型
constructor属性是检测数据类型的最佳方法,但是在框架窗口中检测数组时容易出现问题。
先看一个示例(注意,下面示例在IE浏览器中无法正常运行):
var iframe=document.createElement("iframe"); //创建一个浮动框架 document.body.appendChild(iframe); //在文档中插入该框架 var A=window.frames[0].Array; //获取该浮动框架窗口包含的Array构造函数 var a=new A(); //利用该浮动框架的Array构造函数创建一个数组对象 alert(a.constructor==Array); //返回false alert(a.constructor); //返回Array对象的字符串提示
通过上面示例可以看到,浮动窗口的Array构造函数与当前窗口的Array构造函数并不相等,虽然它们的Array类型结构相同,但是由于所存放的位置不同(属于不同的window),所以结果也不相同。换句话说,使用constructor属性不能够很好地检测框架窗口中的数组类型。为此,需要使用其他方法进行检测。
(1)检测该数组中是否包含数组特有的方法或属性。
function isArray(o){ return o! =null && typeof o==="object"&&"splice" in o &&"join"in o; } alert(isArray(a)); //返回true
该方法先判断值是否为空,如果不为空,则判断是否为object类型,然后探测该对象中是否包含数组特有的方法splice()和join()。如果找到这些方法,则说明该对象是数组类型。
(2)匹配toString()方法返回的字符串。
使用第一种方法也容易造成误解,如果用户自定义了一个包含名称为splice和join的对象,则也会把它检测为数组类型。例如:
var o = { splice:1, join:2 } alert(isArray(o)); //返回true
但是如果把该对象转换为字符串,然后通过检测字符串中是否包含数组所特有的标志字符,也可以确定对象的类型。例如,对于数组对象来说,当直接使用toString()方法时,将转换的字符串作为数组元素的值。如果没有元素,则返回空字符串。
alert([].toString()); //返回""
然而使用call()或者apply()方法调用toString()方法时,返回的字符串就是"[object Array]"。所以可以这样设计:
function isArray(o) { return Object.prototype.toString.call(o) ==="[object Array]"; } alert(isArray(a)); //返回true
在调用toString()方法时,必须指定该方法的作用域路径(原型方法的初始位置),否则系统因为无法找到toString()方法而报错。这样返回的字符串就可以包含“Array”标志字符,然后通过字符串比较,就可以解决跨窗口判定对象是否为数组类型。
4.4.4 案例:设计完善的数据类型检测工具
使用toString()方法可以设计一种更安全的检测JavaScript数据类型的方法,用户还可以根据开发需要进一步补充检测类型的范围。
设计思路:
首先,仔细分析不同类型对象的toString()方法返回值,会发现由Object对象定义的toString()方法返回的字符串形式总是:
[object class]
其中object表示对象的通用类型,class表示对象的内部类型,内部类型的名称与该对象的构造函数名对应。例如,Array对象的class为“Array”, Function对象的class为“Function”, Date对象的class为“Date”,内部Math对象的class为“Math”,所有Error对象(包括各种Error子类的实例)的class为“Error”。
客户端JavaScript的对象和由JavaScript实现定义的其他所有对象都具有预定义的特定class值,如“Window”、“Document”和“Form”等。用户自定义对象的class为“Object”。
class值提供的信息与对象的constructor属性值相似,但是class值是以字符串的形式提供这些信息的,这在特定的环境中是非常有用的。如果使用typeof运算符来检测,则所有对象的class值都为“Object”或“Function”,所以不能够提供有效信息。
但是,要获取对象的class值的唯一方法是必须调用Object的原型方法toString(),因为很多类型对象都会重置Object的toString()方法,所以不能直接调用对象的toString()方法。
例如,下面对象的toString()方法返回的就是当前UTC时间字符串,而不是字符串“[object Date]”。
var d = new Date(); alert(d.toString()); //返回当前UTC时间字符串
调用Object的toString()原型方法,可以通过调用Object.prototype.toString对象的默认toString()函数,再调用该函数的apply()方法在想要检测的对象上执行即可。例如,结合上面的对象d,具体实现代码如下:
var d = new Date(); var m = Object.prototype.toString; alert(m.apply(d)); //返回字符串"[object Date]"
明白了上面的技术细节,下面就是一个比较完整的数据类型安全检测方法源代码:
//安全检测JavaScript基本数据类型和内置对象 //参数:o表示检测的值 //返回值:返回字符串"undefined"、"number"、"boolean"、"string"、"function"、"regexp"、"array"、"date"、"error"、"object"或"null" function typeOf(o){ var _toString = Object.prototype.toString; //获取对象的toString()方法引用 //列举基本数据类型和内置对象类型,你还可以进一步补充该数组的检测数据类型范围 var _type ={ "undefined" : "undefined", "number" : "number", "boolean" : "boolean", "string" : "string", "[object Function]" : "function", "[object RegExp]" : "regexp", "[object Array]" : "array", "[object Date]" : "date", "[object Error]" : "error" } return _type[typeof o] || _type[_toString.call(o)] || (o ? "object" : "null"); //通过把值转换为字符串,然后匹配返回字符串中是否包含特定字符进行检测 }
应用示例:
var a = Math.abs; alert(typeOf(a)); //返回字符串"function"
上述方法适用于JavaScript基本数据类型和内置对象,但是对于自定义对象是无效的。因为自定义对象被转换为字符串后,返回的值是没有规律的,且不同浏览器返回值也是不同的。因此,如果要检测非内置对象,只能够使用constructor属性和instanceof运算符来实现。