判断JS中的数据类型

2017-08-06 | JavaScript

# 前言

在我们编写js的代码中,要处理各种数据。ECMAScript中有5种简单数据类型(也称为基本数据类型):UndefinedNullBooleanNumberString,还有1种复杂数据类型——Object

面对这6种数据类型,我们应该如何做出准确的判断呢?

# typeof运算符

typeof可以解决大部分的数据类型判断,它是一个一元运算,放在一个运算值之前,其返回值为一个字符串

以下是各种数据类型返回结果:

var iStr = "JavaScript";
typeof iStr;    // "string"

var iNum = 10;
typeof iNum;    // "number"

var iBool = true;
typeof iBool;   // "boolean"

var iNull = null;
typeof iNull;   // "object"

var iUndef = undefined;
typeof iUndef;  // "undefined"

var iObj = {};
typeof iObj;    // "object"

var iArr = [];
typeof iArr;    // "object"

var iDate = new Date();
typeof iDate;    // "object"

var iFunc = function() {};
typeof iFunc;   // "function"

从中我们能够发现:

  • null、对象和数组都会返回"object"
  • 可以使用typeof区分nullundefined
  • 函数会返回"function"
  • 暂时无法区分出对象和数组

在IE8和更早版本的IE浏览器中,使用typeof来检测DOM节点(比如document.getElementById())中的函数都返回"object"

// IE8及以下
typeof document.getElementById;           // "object"
typeof document.getElementsByTagName;     // "object"
typeof document.createElement;            // "object"

# instanceof运算符

instanceof运算符希望左操作数是一个对象,右操作数标识对象的类。如果左侧的对象是右侧类的实例,则表达式返回true,否则返回false

var d = new Date();   // 通过Date()创建一个新对象
d instanceof Date;    // true,d是由Date()创建的
d instanceof Object;  // true,所有的对象都是Object的实例
d instanceof Number;  // false,d不是一个Number对象

var a = [1, 2, 3];    // 通过数组字面量新建一个数组
a instanceof Array;   // true,a是一个数组
a instanceof Object;  // true,所有的数组都是对象
a instanceof RegExp;  // false,数组不是正则表达式

function f() {}       // 创建一个函数
f instanceof Function;// true,所有的函数都是Function的实例

使用instanceof,我们可以区分出对象和数组。

instanceof是基于实例与类的关系来判断类型的,如果一个页面中有多个<iframe>,每个<iframe>下的ObjectArrayString等基类都是不同的对象,所以instanceof不能跨<iframe>

# constructor属性

constructor属性返回创建此对象的构造函数的引用。

实际上,一个实例被构造函数创建出来后,其本身是没有constructor属性的,其找到的constructor属性其实是这个实例的原型(通过__proto__访问)下的一个属性。

var obj = {};

obj.hasOwnProperty('constructor');              // false
obj.__proto__.hasOwnProperty('constructor');    // true

所以我们可以想到constructor属性只能用于判断该实例的构造函数的引用,不能像instanceof那样判断该实例所有的类。

var arr = [];                 // 新建一个名为arr的数组

arr.constructor === Array;    // true,arr的构造函数是Array
arr instanceof Array;         // true,arr是Array的实例

arr.constructor === Object;   // false,arr.__proto__.constructor不是Object
arr instanceof Object;        // true,所有数组都是Object的实例

不过该实例的原型对象下的constructor属性是不可靠的,一旦constructor属性被修改或者原型对象被重写,就会触发异常。

var arr = [];                          // 新建一个名为arr的数组

Array.prototype.constructor = Object;  // 修改Array原型对象下constructor的指向

arr.constructor === Array;             // false,arr原型对象下constructor的指向已经不是Array
arr instanceof Array;                  // true,arr是Array的实例

arr.constructor === Object;            // true,arr.__proto__.constructor修改成了Object
arr instanceof Object;                 // true,所有数组都是Object的实例

通过上面的测试可以看出,就算constructor属性被修改了,instanceof的判断依然是可靠的。

# Object.prototype.toString方法

最后介绍一种通用,而且又是最精确的方法。

// 将该方法封装成一个函数
function oToString(o) {
    return Object.prototype.toString.call(o);
}

var iStr = "JavaScript";
oToString(iStr);              // "[object String]"

var iNull = null;
oToString(iNull);             // "[object Null]"

var iUndef = undefined;
oToString(iUndef);            // "[object Undefined]"

var iObj = {};
oToString(iObj);              // "[object Object]"

var iArr = [];
oToString(iArr);              // "[object Array]"

var iDate = new Date();
oToString(iDate);              // "[object Date]"

var iFunc = function() {};
oToString(iFunc);             // "[object Function]"

var iHtml = document.documentElement;
oToString(iHtml);             // "[object HTMLHtmlElement]"

var iBody = document.body;
oToString(iBody);             // "[object HTMLBodyElement]"

实际上,这个方法显示调用了Object.toString(),返回一个表示该对象的字符串。通过判断返回的字符串(大小写不能写错)就能知道是不是我们想要的数据类型。并且这种方法可以跨<iframe>,因为该方法与实例和类的关系无关。

在IE8版本以下nullundefined的结果是"[object Object]"

// IE8
Object.prototype.toString.call(null);         // "[object Object]"
Object.prototype.toString.call(undefined);    // "[object Object]"

# 总结

介绍了四种判断数据类型的方法,最后我来做个比较总结:

  • typeof

    • 优点
      • 能判断大部分的数据类型
      • 运算符的速度快
      • 能判断nullundefined
    • 缺点
      • null、对象和数组的返回结果都是"object"
      • 无法进一步判断对象和数组
  • instanceof

    • 优点
      • 能判断该实例所有的类,可靠
      • 运算符的速度快
      • 能进一步判断对象和数组
    • 缺点
      • 无法跨<iframe>判断
  • constructor

    • 优点
      • 实际上是判断该实例原型下的constructor属性
    • 缺点
      • 如果原型下的constructor属性被修改,结果不可靠
      • 因为查找的是属性,所以比运算符的速度慢
  • Object.prototype.toString

    • 优点
      • 实际上是显示调用Object.toString()
      • 通用,所有数据类型都可以精确区分
      • 能够跨<iframe>判断
    • 缺点
      • 因为调用的是方法,所以速度最慢

Object.prototype.toString虽然通用,不过性能最差,我是只有在其他方法解决不了的情况下,才会选用它。以上方法需要根据需求,灵活使用,以求代码性能的最大化。