判断 JavaScript 数据类型的几种方式

typeof

typeof是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 7 种:numberbooleansymbolstringobjectundefinedfunction 等。

1
typeof '';             // string 有效
2
typeof 1;              // number 有效
3
typeof Symbol();       // symbol 有效
4
typeof true;           // boolean 有效
5
typeof undefined;      // undefined 有效
6
typeof null;           // object 无效
7
typeof [] ;            // object 无效
8
typeof new Function(); // function 有效
9
typeof new Date();     // object 无效
10
typeof new RegExp();   // object 无效

缺点:

1
typeof 对于引用类型中的数组、日期、正则等类型的处理,只返回了处于其原型链最顶端的 Object 类型;
2
对应 null 返回的是 "object",而 null 有自己的数据类型 Null。

instanceof

instanceof 是用来判断 a 是否为 B 的实例,表达式为:a instanceof B,如果 aB 的实例,则返回 true,否则返回 false在这里需要特别注意的是:instanceof 检测的是原型对象。

1
// 伪代码实现
2
instanceof (a,B) = {
3
    var L = a.__proto__;
4
    var R = B.prototype;
5
    if(L === R) {
6
        // A的内部属性 __proto__ 指向 B 的原型对象
7
        return true;
8
    }
9
    return false;
10
}

从上述过程可以看出,当 a__proto__ 指向 B 的原型对象时,就认为 a 就是 B 的实例,我们再来看几个例子:

1
[] instanceof Array; // true
2
{} instanceof Object;// true
3
new Date() instanceof Date;// true
4
 
5
function Person(){};
6
new Person() instanceof Person;
7
 
8
[] instanceof Object; // true
9
new Date() instanceof Object;// true
10
new Person instanceof Object;// true

我们发现,虽然 instanceof 能够判断出 []Array 的实例,但它认为 [] 也是 Object 的实例,为什么呢?

我们来分析一下 []ArrayObject 三者之间的关系:

instanceof 能够判断出 [].__proto__ 指向 Array.prototype,而 Array.prototype.__proto__ 又指向了 Object.prototype,最终 Object.prototype.__proto__指向了 null,标志着原型链的结束。

因此,[]ArrayObject 就在内部形成了一条原型链:

849589-20160112232510850-2003340583.png

从原型链可以看出,[]__proto__ 直接指向 Array.prototype,间接指向 Object.prototype,所以按照 instanceof 的判断规则,[] 就是 Object 的实例。依次类推,类似的 new Date()new Person() 也会形成一条对应的原型链 。

缺点

1
instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。

拓展

instanceof 操作符的问题在于,它假定只有一个全局执行环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的构造函数。如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。

1
var iframe = document.createElement('iframe');
2
document.body.appendChild(iframe);
3
xArray = window.frames[0].Array;
4
var arr = new xArray(1,2,3); // [1,2,3]
5
arr instanceof Array; // false

Array.isArray()

针对数组的这个问题,ES5 提供了 Array.isArray() 方法。该方法用以确认某个对象本身是否为 Array 类型,而不区分该对象在哪个环境中创建。

1
if (Array.isArray(value)){
2
   //对数组执行某些操作
3
}

Array.isArray() 本质上检测的是对象的 [[Class]] 值,[[Class]] 是对象的一个内部属性,里面包含了对象的类型信息,其格式为 [object Xxx]Xxx 就是对应的具体类型。对于数组而言,[[Class]] 的值就是 [object Array] 。

toStrig()

toString()Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]]。这是一个内部属性,其格式为 [object Xxx],其中 Xxx 就是对象的类型。

对于 Object 对象,直接调用 toString() 就能返回 [object Object]。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

1
Object.prototype.toString.call('') ;   // [object String]
2
Object.prototype.toString.call(1) ;    // [object Number]
3
Object.prototype.toString.call(true) ; // [object Boolean]
4
Object.prototype.toString.call(Symbol()); //[object Symbol]
5
Object.prototype.toString.call(undefined) ; // [object Undefined]
6
Object.prototype.toString.call(null) ; // [object Null]
7
Object.prototype.toString.call(new Function()) ; // [object Function]
8
Object.prototype.toString.call(new Date()) ; // [object Date]
9
Object.prototype.toString.call([]) ; // [object Array]
10
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
11
Object.prototype.toString.call(new Error()) ; // [object Error]
12
Object.prototype.toString.call(document) ; // [object HTMLDocument]
13
Object.prototype.toString.call(window) ; //[object Window] window 是全局对象

constructor

当一个函数 F 被定义时,JS 引擎会为 F 添加 prototype 原型对象,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用。

如下所示:

849589-20170508125250566-1896556617.png

当执行 var f = new F() 时,F 被当成了构造函数,fF 的实例对象,此时 F 原型上的 constructor 传递到了 f 上,因此 f.constructor == F

849589-20170508125714941-1649387639.png

可以看出,F 利用原型对象上的 constructor 引用了自身,当 F 作为构造函数来创建对象时,原型上的 constructor就被遗传到了新创建的对象上, 从原型链角度讲,构造函数 F 就是新对象的类型。这样做的意义是,让新对象在诞生以后,就具有可追溯的数据类型。

同样,JavaScript 中的内置对象在内部构建时也是这样做的:

849589-20170508131800457-2091987664.png

细节问题

nullundefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。

函数的 constructor 是不稳定的,这个主要体现在自定义对象上,当开发者重写 prototype 后,原有的 constructor 引用会丢失,constructor 会默认为 Object

849589-20170508132757347-1999338357.png

因为 prototype 被重新赋值的是一个 {}{}new Object() 的字面量,因此 new Object() 会将 Object 原型上的 constructor 传递给 {},也就是 Object 本身。

因此,为了规范开发,在重写对象原型时一般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改。

总结

  • typeof 检测引用类型和 null 时,除了函数都会显示 "object"
  • 检测函数使用推荐使用 typeof
  • instanceof: 检测对象是否是某个构造函数的实例。
  • Array.isArray(): 判断是否为 Array 数据类型时使用。
  • constructor: 可以使用 constructor 属性验证实例的原始类型(与操作符 instanceof 非常类似)。
  • toString(): 万能。
1
检测对象的属性是否存在使用 in 运算符或者使用 Object.hasOwnProperty()

本节参考:一像素-判断数据类型的四种方法