Javascript数据类型之类型检查

  如果你只想知道不同情况下使用何种方式来进行类型检查,请直接看总结。如果你还想进一步的了解不同方式的特点,请直接看每一节的小结。当然我更希望您能耐心看完全文。
 Javascript数据类型之基本数据类型中,我们介绍了javascript中的基本数据类型,本文将介绍如何做类型检查。其实在上一节中我们已经使用了typeof函数来进行类型的检查,typeof就够用了嘛,还有哪些其他的类型检查方法,它们分别有什么样的优缺点,适用于怎样的场景。下面将对这些问题做详细的说明。

1. typeof

  我们能够使用typeof来判断数据类型,示例如下:

    typeof "jerry"; // "string"
    typeof 12;  // "number"
    typeof true;    // "boolean"
    typeof undefined;   // "undefined"
    typeof null;    // "object"
    typeof {name: "jerry"}; // "object"
    typeof function(){};    // "function"

  结合上例,可以看出typeof可以判断原始类型(Null除外),可以识别出对象和函数。typeof能够识别出函数对象,也能够识别出其他对象吗?让我们再看一个示例:

    typeof [];          // "object"
    typeof new Date;    // "object"
    typeof /\d/;        // "object"
    function Person(){};
    typeof new Person;  // "object"

  结合上例我们可以看出typeof无法识别具体的对象类型(除了function对象)。

小结:

typeof可以判断原始类型(Null除外), 不能判断具体的对象类型(Function除外)。

2.Object.prototype.toString

  通过上一节对typeof的介绍,我们了解到typeof无法判断对象的具体类型,那我们如何来判断对象的具体类型呢?我们可以通过对象的内部属性[[Class]]来判断对象的具体类型,而通过Object.prototype.toString方法可以获取对象的[[Class]]属性。本节示例如下:

    var toClass = Object.prototype.toString;
    toClass.call([]);           // "[object Array]"
    toClass.call(new Date);     // "[object Date]"
    toClass.call(/\d/);         // "[object RegExp]"

  判断类型我们只需要[[Class]]相关的信息,我们可以对Object.prototype.toString进行一层封装,如下:

    function type(obj){
        return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
    }

  Object.prototype.toString作用在String/Number/Boolean类型上时,String/Number/Boolean类型会先被包装成String/Number/Boolean对象,然后正确的返回。Object.prototype.toString作用在Undefined/Null,也能正确返回。本节示例如下:

    type("jerry");      // "string"
    type(1);            // "number"
    type(true);         // "boolean"
    type(undefined);    // "undefined"
    type(null);         // "null"

  Object.prototype.toString不能判断自定义类型。本节示例如下:

    function Person(name){
        this.name = name;
    }
    type(new Person("jerry"));  // "object"

小结:

使用Object.prototype.toString可以判断原始类型以及内置(build-in)对象类型,但不能判断自定义类型。

3. .constructor

  我们可以通过对象的constructor属性来判断对象的具体类型,包括自定义类型。本节示例如下:

    // 判断内置对象类型
    [].constructor === Array;   // true
    function Person(name){
        this.name = name;
    }
    // 判断自定义对象
    new Person("jerry").constructor === Person; // true
    // 判断原始类型
    "jerry".constructor === String;     // true
    (1).constructor === Number;         // true
    true.constructor === Boolean;       // true

  Undefined/Null类型不是对象,也不能包装成对象,没有constructor方法,自然也不就能用这种方法判断。
  当继承的时候, 使用constructor属性的方式无法判断子类对象与父类之间的关系。本节示例如下:

function Person(name){
        this.name = name;
    }
function Student(name, grade){
    Person.apply(this, [].slice.call(arguments));
    this.grade = grade;
}
Student.prototype = new Person;
Student.prototype.constructor = Student;

var jerry = new Student("jerry", 3);
jerry.constructor === Student;  // true

  在上面的示例中,jerry是一个Student, 按照继承的概念,jerry也是一个Person,但使用constructor属性的方式,无法判断这层关系。
  因为对象的constructor是一个函数,函数是引用类型, 所以使用constructor属性的方式还存在跨window问题。假设页面a.html里面有一个iframe b.html, b.html里面有个数组变量barr, a.html获取到arr,并赋值给aarr, 如下图所示:

  这时在a.html里执行aarr.constructor === Array为false,这就是跨window问题。因为每个window都有自己的内置对象,a.html里的Array和b.html的Array是不同的,barr使用b.html里的Array构造的。我们可以将constructor转换成字符串,然后通过判断构造函数名称来判断类型,这样就能解决跨window的问题。示例如下

// 获取对象构造函数名称
function getClassName(obj){
    return obj && obj.constructor && obj.constructor.toString().match(/function\s*([^(]*)/)[1];
}
getClassName(aarr) === "Array"; // true

  如上例所示,getClassName函数获取对象构造函数名称,因为字符串是值类型,, 所以不存在跨window问题。

小结:

使用constructor属性的方式能够判断原始类型(Undefined/Null除外)、内置对象类型、自定义对象类型,但不能判断子类对象和父类的关系;有跨window问题,但可以解决。

4. instanceof

  instanceof会将右操作数与原型链上的constructor的属性逐一比对来判断对象具体类型的,所以能够判断继承情形下子类对象与父类的关系。假设我们已有继承关系, Student继承自Person。本节示例如下:

var jerry = new Student("jerry", 3);
jerry instanceof Student;   // true
jerry instanceof Person;    // true

  上例中jerry instanceof Person语句,首先将右操作数Person与jerry原型(Student.prototype)的constructor属性(指向Student构造函数)比较,发现不相等;再沿着原型链查找,将右操作数Person与jerry原型的原型(Person.prototype)的constructor属性(指向Person构造函数)比较,发现相等,返回true.
  instanceof不仅能够判断自定义对象,还能够判断内置对象类型,但不能判断原始类型。本节示例如下:

    // 能够判断内置对象类型
    [] instanceof Array;    // true
    /\d/ instanceof RegExp; // true
    // 不能判断原始类型
    1 instanceof Number;    // false
    "jerry" instanceof String;   // false

  因为instanceof的右操作数是构造函数,是引用类型,所以使用instanceof的方式也存在跨window的问题。

小结:

使用instanceof能够判断内置对象类型,能够判断自定义对象类型,并能判断子类对象和父类的关系;不能判断原始类型,有跨window问题。

总结

  下表是对上文介绍的4种判断类型方式的总结比较,大家可以根据应用场景选用不同的方式。



  上表后4列分别表示是否能够判断原始类型、是否能够判断内置对象类型、是否能够判断自定义类型、是否能够处理继承关系(继承时子类对象与父类关系的判断)、是否能够跨window。(^**)表示**除外,比如第2行第3列×(^Function)表示typeof不能判断内置对象类型,函数类型除外。第4行第6列×(√)表示使用constructor的方式有跨window的问题,但可修复。
  当判断非自定义类型时,推荐Object.prototype.toString的方式,使用封装的type函数。
进一步了解Javascript类型系统:

本文来自网易实践者社区,经作者魏文庆授权发布。