判断js中的数据类型

Posted by Jiazhi on 2016-08-06

前言

在我们编写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虽然通用,不过性能最差,我是只有在其他方法解决不了的情况下,才会选用它。以上方法需要根据需求,灵活使用,以求代码性能的最大化。