UI-javascript变量类型该如何判断

在JavaScript中,有5种基本数据类型和1种复杂数据类型,基本数据类型有:UndefinedNullBooleanNumberString;复杂数据类型是Object
Object
中还细分了很多具体的类型,比如:ArrayFunctionDate等等。今天我们就来探讨一下,使用什么方法判断一个出一个变量的类型。

在讲解各种方法之前,我们首先定义出几个测试变量,看看后面的方法究竟能把变量的类型解析成什么样子,以下几个变量差不多包含了我们在实际编码中常用的类型

 

1. 使用typeof检测

typeof 操作符返回的是类型字符串,它的返回值有6种取值:

 

 

所有对象的
typeof
都是
"object"
,不能用于检测用户自定义类型。 比如
Date
,
RegExp
,
Array
,
DOM Element
的类型都是
"object"

typeof
还有一个知名的bug:

 

 
null
是基本数据类型,它的类型显然是
Null
。其实这也反映了
null
的语义, 它是一个空指针表示对象为空,而
undefined
才表示什么都没有。 总之,typeof只能用于基本数据类型检测,对于
null
还有Bug。

2. 使用instanceof检测

instanceof操作符用于检查某个对象的原型链是否包含某个构造函数的
prototype
属性。例如:

 

 
obj
的原型链上有很多对象(成为隐式原型),比如:
obj.__proto__
,
obj.__proto__.__proto__
, …
如果这些对象里存在一个
p === Widget.prototype
,那么
instanceof
结果为
true
,否则为
false

instanceof
是通过原型链来检查类型的,所以适用于任何”object”的类型检查。

 

 
instanceof
也可以用来检测内置兑现,比如
Array
,
RegExp
,
Object
,
Function

 

 
instanceof
对基本数据类型不起作用,因为基本数据类型没有原型链。

 

 

但你可以这样:

 

 

但这时你已经知道数据类型了,类型检查已经没有意义了。

3. 使用constructor检测

constructor属性返回一个指向创建了该对象原型的函数引用。需要注意的是,该属性的值是那个函数本身。例如:

 

 
constructor
不适合用来判断变量类型。首先因为它是一个属性,所以非常容易被伪造:

 

 

另外
constructor
指向的是最初创建当前对象的函数,是原型链最上层的那个方法:

 

 


instanceof
类似,
constructor
只能用于检测对象,对基本数据类型无能为力。 而且因为
constructor
是对象属性,在基本数据类型上调用会抛出
TypeError
异常:

 

 


instanceof
不同的是,在访问基本数据类型的属性时,JavaScript会自动调用其构造函数来生成一个对象。例如:

 

 

这种将一个值类型转换为对象引用类型的机制在其他语言中也存在,在C#中称为装箱(Boxing)。

4. 使用Object.prototype.toString.call

我们先不管这个是什么,先来看看他是怎么检测变量类型的:

 

从输出的结果来看,

Object.prototype.toString.call(变量)
输出的是一个字符串,字符串里有一个数组,第一个参数是Object,第二个参数就是这个变量的类型,而且,所有变量的类型都检测出来了,我们只需要取出第二个参数即可。或者可以使用
Object.prototype.toString.call(arr)=="object Array"
来检测变量arr是不是数组。

我们现在再来看看ECMA里是是怎么定义
Object.prototype.toString.call
的:

Object.prototype.toString( ) When the toString method is called, the following steps are taken:

  1. Get the [[Class]] property of this object.
  2. Compute a string value by concatenating the three strings “[object “, Result (1), and “]”.
  3. Return Result (2)

上面的规范定义了Object.prototype.toString的行为:首先,取得对象的一个内部属性[[Class]],然后依据这个属性,返回一个类似于”[object Array]”的字符串作为结果(看过ECMA标准的应该都知道,[[]]用来表示语言内部用到的、外部不可直接访问的属性,称为“内部属性”)。利用这个方法,再配合call,我们可以取得任何对象的内部属性[[Class]],然后把类型检测转化为字符串比较,以达到我们的目的。

5. jquery中$.type的实现

在jquery中提供了一个
$.type
的接口,来让我们检测变量的类型:

 

看到输出结果,有没有一种熟悉的感觉?对,他就是上面使用

Object.prototype.toString.call(变量)
输出的结果的第二个参数呀。

我们这里先来对比一下上面所有方法检测出的结果,横排是使用的检测方法, 竖排是各个变量:

 

 

这样对比一下,就更能看到各个方法之间的区别了,而且
Object.prototype.toString.call

$type
输出的结果真的很像。我们来看看jquery(2.1.2版本)内部是怎么实现$.type方法的:

 

我们先来看看jQuery.each的这部分:

的值是: class2type = { ‘[object Boolean]’ : ‘boolean’, ‘[object Number]’ : ‘number’, ‘[object String]’ : ‘string’, ‘[object Function]’: ‘function’, ‘[object Array]’ : ‘array’, ‘[object Date]’ : ‘date’, ‘[object RegExp]’ : ‘regExp’, ‘[object Object]’ : ‘object’, ‘[object Error]’ : ‘error’ }[/crayon]
 

再来看看

type
方法:

 

typeof obj === "object" || typeof obj === "function"
时,就返回
class2type[ toString.call(obj)
。到这儿,我们就应该明白为什么Object.prototype.toString.call和$.type那么像了吧,其实jquery中就是用
Object.prototype.toString.call
实现的,把'[object Boolean]’类型转成’boolean’类型并返回。若class2type存储的没有这个变量的类型,那就返回”object”。 除了”object”和”function”类型,其他的类型则使用typeof进行检测。即
number
,
string
,
boolean
类型的变量,使用typeof即可。

跨窗口问题

我们知道Javascript是运行在宿主环境下的,而每个宿主环境会提供一套ECMA标准的内置对象,以及宿主对象(如
window
,
document
),一个新的窗口即是一个新的宿主环境。 不同窗口下的内置对象是不同的实例,拥有不同的内存地址。


instanceof

constructor
都是通过比较两个
Function
是否相等来进行类型判断的。 此时显然会出问题,例如:

 

 

因此
iWindow
中的数组
arr
原型链上是没有
window.Array
的。请看:

 

toString

toString
方法是最为可靠的类型检测手段,它会将当前对象转换为字符串并输出。
toString
属性定义在
Object.prototype
上,因而所有对象都拥有
toString
方法。 但
Array
,
Date
等对象会重写从
Object.prototype
继承来的
toString
, 所以最好用
Object.prototype.toString
来检测类型。

 

 

因为返回值是字符串,也避免了跨窗口问题。当然IE弹窗中还是有Bug,不必管它了。 现在多少人还在用IE?多少人还在用弹窗?


Object.prototype.toString
类似地,
Function.prototype.toString
也有类似功能, 不过它的
this
只能是
Function
,其他类型(例如基本数据类型)都会抛出异常。

总结


  •  

有时Duck Typing的方式也非常可行,貌似已经成为了前端的惯例。 比如jQuery是这样判断一个Window的: