javascript中类型检测

一、Javascript语言定义的5种原始类型及6种基本数据类型

1、javascript中定义的5种原始类型分别为:
null、undefined、number、string、boolean
2、javascript中定义的6种基本数据类型分别为:
null、undefined、number、string、boolean、object

二、检测原始值

如果你希望一个值是字符串、数字、布尔值或undefined,最佳选择是使用typeof运算符。typeof运算符会返回一个表示值的类型的字符串。

  • 对于字符串,typeof返回”string”
  • 对于数字,typeof返回”number”
  • 对于布尔值,typeof返回”boolean”
  • 对于undefined,typeof返回”undefined”

typeof的基本语法是:
typeof variable
你也可以这样使用typeof:
typeof(variable)
尽管这是合法的javascript语法,但这种方法让typeof看起来像一个函数而非运算符。鉴于此,我们推荐无括号的写法。
使用typeof来检测以下4种原始值类型是非常安全的做法。

//检测字符串
if(typeof name === "string"){
    anotherName = name.substring(3);
}

//检测数字
if(typeof count === "number"){
    updateCount(count);
}

//检测布尔值
if(typeof found === "boolean" && found){
    console.info("Found!");
}

//检测undefined
if(typeof MyApp === "undefined"){
    MyApp = {
        //相关代码
    };
}  

typeof运算符的独特之处在于,将其用于一个未声明的变量也不会报错。未定义的变量和值为undefined的变量通过typeof都将返回”undefined”。运行typeof null则返回”object”,这是一种低效的判断null的方法。如果需要检测null,则直接使用恒等运算(===)或非恒等运算(!==)。

三、检测引用值

引用值也称作对象(object)。在Javascript中除了原始值之外的值都是引用。有以下几种内置的引用类型:Object、Array、Date、Error等。使用typeof运算符在判断这些引用类型时则会显得力不从心,因为所有对象都会返回”object”。typeof另外一种不推荐的用法是当检测null的类型时,typeof运算符用于null时将会返回”object”。
检测某个引用值的类型的最好方法是使用instanceof运算符。
instanceof的基本语法是:
value instanceof constructor

//检测日期
if(value instanceof Date){
    console.log(value.getFullYear());
}

//检测正则表达式
if(value instanceof RegExp){
    if(value.test(anotherValue)){
        console.log("Matches");
    }
}  

//检测Error
if(value instanceof Error){
    throw value;
}  

instanceof的一个特性是它不仅检测构造这个对象的构造器,还检测原型链。原型链包含了很多信息,包括定义对象所采用的继承模式。比如,默认情况下,每个对象都继承自Object,因此每个对象的value instanceof Object都会返回true。比如:

var now = new Date();
console.log(now instanceof Object);//true
console.log(now instanceof Date);//true

因此使用instanceof来判断对象是否属于某个特定类型的做法并非最佳选择。
instanceof运算符也可以检测自定义类型,比如:

function Person(name){
    this.name = name;
} 
var me = new Person("sem");
console.log(me instanceof Object);//true
console.log(me instanceof Person);//true  

在Javascript中检测自定义类型时,最好的做法就是使用instanceof运算符,这也是唯一的办法。假设一个浏览器帧(frame A)里的一个对象被传入到另一个帧(frame B)中。两个帧都定义了构造函数Person。如果来自帧A的对象是帧A的Person的实例。则如下规则成立。

//true
frameAPersonInstance instanceof frameAPerson

//false
frameAPersonInstance instanceof frameBPerson  

因为每个帧(frame)都拥有Person的一份拷贝,它被认为是该帧(frame)中的Person的拷贝的实例。

四、检测函数

从技术上讲,Javascript中的函数是引用类型,同样存在Function构造函数,每个函数都是实例,比如:

function myFunc(){}
//不好的写法
console.log(myFunc instanceof Function);//true

然而,这个方法不能跨帧使用,每个帧都有各自的Function构造函数。好在typeof运算符可以用于函数,返回”function”。

function myFunc(){}
//好的写法
console.log(typeof myFunc === "function");//true  

检测函数最好的方法是使用typeof,因为它可以跨帧使用。用typeof来检测函数有一个限制。在IE8和更早版本的IE浏览器中,使用typeof来检测DOM节点(比如document.getElementById())中的函数都返回”object”而不是”function”。
之所以出现这种怪异的现象是因为浏览器对DOM的实现有差异。简而言之,这些早期版本的IE并没有将DOM实现为内置的Javascript方法,导致内置的typeof运算符将这些函数标识为对象。开发者往往通过in运算符来检测DOM的方法,比如:

//检测DOM方法
if("querySelectorAll" in document){
    images = document.querySelectorAll("img");
}

在其他所有的情形中,typeof运算符是检测javascript函数的最佳选择。

五、检测数组

关于如何在Javascript中检测数组类型已经有很多研究了,最终Kangax给出了一种优雅的解决方案。

function isArray(value){
    return Object.prototype.toString.call(value) === "[object Array]";
}

Kangax发现调用某个值的内置toString()方法在所有浏览器中都会返回标准的字符串结果。对于数组来说,返回的字符串为”[object Array]”,也不用考虑数组实例是在哪个帧中被构造出来。这种方法在识别内置对象时往往十分有用,但对于自定义对象请不要用这种方法。

六、检测属性

另外一种用到null(以及undefined)的场景是当检测一个属性是否在对象中存在时,比如:

//不好的写法:检测假值
if(object[propertyName]){
    //一些代码
}

//不好的写法:和null相比较
if(object[propertyName] != null){
    //一些代码
}

//不好的写法:和undefined比较
if(object[propertyName] != undefined){
    //一些代码
}

以上这段代码里的每个判断,实际上是通过给定的名字来检查属性的值,而非判断给定的名字所指的属性是否存在,因为当属性值为假值结果会出错,比如0、””、false、null、undefined。比如如果属性记录了一个数字,则这个值可以是零。这样的话,上段代码第一个判断就会导致错误。以此类推,如果属性值为null或者undefined时,三个判断都会导致错误。
判断属性是否存在最好的方法是使用in运算符。in运算符仅仅会简单地判断属性是否存在,而不会去读取属性的值,这样就避免了前文提到的有歧义的语句。如果实例对象的属性存在、或者继承自对象原型,in运算符都会返回true。比如:

var object = {
    count:0,
    related : null
};

//好的写法
if("count" in object){
    //这里的代码会执行
}

//不好的写法:检测假值
if(object["count"]){
    //这里的代码不会执行
}

//好的写法
if("related" in object){
    //这里代码会执行
}

//不好的写法:检测是否为null
if(object["related"] != null){
    //这里代码不会执行
}

如果你只想检查实例对象的某个属性是否存在,则使用hasOwnProperty()方法。所有继承自Object的Javascript对象都有这个方法,如果实例中存在这个属性则返回true(如果这个属性只存在原型里,则返回false)。需要注意的是,在IE8以及更早版本的IE中,DOM对象并非继承自Object,因此也不会包含这个方法。也就是说,你在调用DOM对象的hasOwnProperty()方法之前应当先检测其是否存在。

//对于所有非DOM对象来说,这是好的写法
if(Object.hasOwnProperty("related")){
    //执行这里的代码
}  

//如果你不确定为DOM对象,则这样来写
if("hasOwnProperty" in object && object.hasOwnProperty("related")){
    //执行这里的代码
}  

不管你什么时候需要检测属性的存在性,请使用in运算符或者hasOwnProperty(),这样做可以避免很多Bug。

Fork me on GitHub