了解到了JavaScript的变量主要有基本类型(undefined、null、boolean、number和string, ES6中还新增了Symbol)和引用类型(对象、数组、函数)。但在JavaScript中用户定义的类型(object)并没有类的声明,因此继承关系只能通过构造函数和原型链接来检查。而在这篇文章中,主要整理了在JavaScript中如何检测一个变量的类型。
在JavaScript中常见的类型检查手段主要有:typeof、instanceof、constructor和toString几种。接下来主要看看这几种类型检查手段的使用与区别之处。
1:type of(操作符返回的是字符串,它的返回值如下)
Undefined>>>>Undefined Null>>>"object"
布尔值>>>>"boolean" 数值 >>>"number"
字符串>>>"string" Symbol>>>"Symbol"
函数对象>>>"function" 任何其他对象>>"object"
宿主对象(JS环境提供的,比如浏览器) >>>> Implementation-dependent
typeof ["w3cplus","大漠"];
typeof new Date();
typeof new String("w3cplus");
typeof new function (){};
typeof /test/i; 返回的都是object
另外对于Null,typeof检测返回的值也是一个object:
这是typeof的一个知名Bug。先忽略其是不是typeof的bug,在JavaScript中,null也是基本数据类型之一,它的类型显然是Null。其实这也反映了null的语义,它是一个空指针表示对象为空,而undefined才表示什么都没有
根据上面的内容,简单的对typeof做一个归纳:
typeof只能检测基本数据类型,对于null还有一个Bug。
在实际开发中使用typeof时需要养成一个好的习惯。比如,使用typeof一个较好的习惯是写一个多种状态的函数:
function f (x) { if (typeof x == "function") { ... // 当x是一个函数时,做些什么... } else { ... // 其它状态 } }
,前面使用typeof 对一个数组做检测的时候也返回object
那么在JavaScrit中,可以通过创建一个函数,并且通过一些正则表达式,让这个函数实现一个改进版本的typeof。如下所示:
function toType (obj) {
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
}
之后我们做测试,则如下:
toType({name: "大漠"}); // => "object"
toType(["W3cplus","大漠"]); // => "array"
(function() {console.log(toType(arguments))})(); // => arguments
toType(new ReferenceError); // => "error"
toType(new Date); // => "date"
toType(/a-z/); // => "regexp"
toType(Math); // => "math"
toType(JSON); // => "json"
toType(new Number(4)); // => "number"
toType(new String("abc")); // => "string"
toType(new Boolean(true)); // => "boolean"
toType(function foo() {console.log("Test")}); // =>"function"
2:instanceof(操作符用于检测某个对象的原型链是否包含某个构造函数的prototype属性)
function C(){}
function D(){}
var o = new C();
// true,因为 Object.getPrototypeOf(o) === C.prototype
o instanceof C;
// false,因为 D.prototype不在o的原型链上
o instanceof D;o对象的原型链上有很多对象(成为隐式原型),比如o.proto,o.proto.proto等等。因为 Object.getPrototypeOf(o) === C.prototype所以返回的是true,而D.prototype不在o的原型链上,所以返回的是false。
需要注意的是,如果表达式 o instanceof C 返回true,则并不意味着该表达式会永远返回ture,因为C.prototype属性的值有可能会改变,改变之后的值很有可能不存在于o的原型链上,这时原表达式的值就会成为false。另外一种情况下,原表达式的值也会改变,就是改变对象o的原型链的情况,虽然在目前的ES规范中,我们只能读取对象的原型而不能改变它,但借助于非标准的proto魔法属性,是可以实现的。比如执行o.proto = {}之后,o instanceof C就会返回false了。
instanceof是通过原型链来检查类型的。所谓的“类型”是一个构
造函数。例如:
// 比如直接原型关系
function Animal () {};
var a = new Animal();
a instanceof Animal; // => true
// 原型链上的间接原型
function Cat() {};
Cat.prototype = new Animal;
var b = new Cat();
b instanceof Animal; // =>
instanceof除了适用于任何object的类型检查之外,也可以用来检测内置兑现,比如:Array、RegExp、Object、Function:
[1, 2, 3] instanceof Array // true
/abc/ instanceof RegExp // true
({}) instanceof Object // true
instanceof对基本数据类型检测不起作用,主要是因为基本数据类型没有原型
但是还可以用的:如下:
new Number(3) instanceof Number // true
new Boolean(true) instanceof Boolean // true
new String('abc') instanceof String // true
不过这个时候,都知道数据类型了,再使用instanceof来做检测就毫无意义了。
简单总结一下
instanceof适用于检测对象,它是基于原型链运作的。
3:constructor(返回一个指向创建了该对象原型的函数引用。需要注意的是,该属性的值是那个函数本身)
function Animal () {};
var a = new Animal;
a.constructor === Animal; // => true
constructor不适合用来判断变量类型。首先因为它是一个属性,所以非常容易伪造:
var a = new Animal;
a.constructor === Array;
a.constructor === Animal; // => false
另外constructor指向的是最初创建当前对象的函数,是原型链最上层的那个方法:
function Cat () { };
Cat.prototype = new Animal;
function BadCat () { };
BadCat.prototype = new Cat;
var a = new BadCat;
a.constructor === Animal; // => true
Animal.constructor === Function; // => true
与instanceof类似,constructor只能用于检测对象,对基本数据类型无能为力。而且因为constructor是对象属性,在基本数据类型上调用会抛出TypeError异常:
null.constructor; // => TypeError
undefined.constructor; // => TypeError
和instanceof不同的是,在访问基本数据类型的属性时,JavaScript会自动调用其构造函数来生成一个对象,如:
.constructor === Number // true
true.constructor === Boolean // true
'abc'.constructor === String // true
// 相当于
(new Number(3)).constructor === Number
(new Boolean(true)).constructor === Boolean
(new String('abc')).constructor === String
另外,使用constructor有两个问题。第一个问题它不会走原型链:
function Animal () {};
function Cat () {};
Cat.prototype = new Animal;
Cat.prototype.constructor = Cat;
var felix = new Cat;
felix.constructor === Cat; // => true
felix.constructor === Animal; // => false
就是null和undefined使用constructor会报异常
同样对constructor做一个简单的总结
constructor指向的是最初创建者,而且易于伪造,不适合做类型判断。
跨窗口问题
JavaScript是运行在宿主环境下的,而每个宿主环境都会提供一套标准的内置对象,以及宿主对象(如window,document),一个新的窗口即是一个新的宿主环境。不同的窗口下的内置对象是不同的实例,拥有不同的内存地址。
而instanceof和constructor都是通过比较两个Function是否相等来进行类型判断的。 此时显然会出问题,例如:
var iframe = document.createElement('iframe');
var iWindow = iframe.contentWindow;
document.body.appendChild(iframe);
iWindow.Array === Array // false
// 相当于
iWindow.Array === window.Array // false
因此iWindow中的数组arr原型链上是没有window.Array的
toString
最简单的数据类型检测方法应当算是toString,不过其看起来像是一个黑魔法:
toString属性定义在Object.prototype上,因而所有对象都拥有toString方法。默认情况之下,调用{}.toString()(一个object),将会得到[object object]。
我们可以通过.call()来改变这种情况(因为它将其参数转换为值类型)。例如,通过使用.call(/test/i)(正则表达多),这个时候[object object]将变成[object RegExp]。
Object.prototype.toString.call([]); // => [object Array]
Object.prototype.toString.call({}); // => [object Object]
Object.prototype.toString.call(''); // => [object String]
Object.prototype.toString.call(new Date()); // => [object Date]
Object.prototype.toString.call(1); // => [object Number]
Object.prototype.toString.call(function () {}); // => [object Function]
Object.prototype.toString.call(/test/i); // => [object RegExp]
Object.prototype.toString.call(true); // => [object Boolean]
Object.prototype.toString.call(null); // => [object Null]
Object.prototype.toString.call(); // => [object Undefined]
不过toString也不是十全十美的,因为它无法检测用户自定义类型。主要是因为Object.prototype是不知道用户会创造什么类型的,它只能检测ECMA标准中的那些内置类型。
function Animal () {};
Object.prototype.toString.call (Animal); // => [object Function]
Object.prototype.toString.call (new Animal); // => [object Object]
和Object.prototype.toString类似,Function.prototype.toString也有类似功能,不过它的this只能是Function,其它类型(如基本数据类型)都会抛出异常。
自定义检测数据类型的函数
通过前面的内容介绍,我们可以获知:
typeof只能检测基本数据类型,对于null还有Bug;
instanceof适用于检测对象,它是基于原型链运作的;
constructor指向的是最初创建者,而且容易伪造,不适合做类型判断;
toString适用于ECMA内置JavaScript类型(包括基本数据类型和内置对象)的类型判断;
基于引用判等的类型检查都有跨窗口问题,比如instanceof和constructor。总之,如果你要判断的是基本数据类型或JavaScript内置对象,使用toString; 如果要判断的是自定义类型,请使用instanceof。
其实,为了便于使用,可以在toString的基础上封闭一个函数。比如@toddmotto写的axis.js:
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.axis = factory();
}
}(this, function () {
'use strict';
var axis = {};
var types = 'Array Object String Date RegExp Function Boolean Number Null Undefined'.split(' ');
function type() {
return Object.prototype.toString.call(this).slice(8, -1);
}
for (var i = types.length; i--;) {
axis['is' + types[i]] = (function (self) {
return function (elem) {
return type.call(elem) === self;
};
})(types[i]);
}
return axis;
}));
有了这个函数,咱们只需要像下面这样使用,就可以检测数据类型:
axis.isArray([]); // true
axis.isObject({}); // true
axis.isString(''); // true
axis.isDate(new Date()); // true
axis.isRegExp(/test/i); // true
axis.isFunction(function () {}); // true
axis.isBoolean(true); // true
axis.isNumber(1); // true
axis.isNull(null); // true
axis.isUndefined(); // true
总结
typeof instanceof constructor toString
避免字符串比较 No Yes Yes No
常用的 Yes Yes No No
检查自定义类 No Yes Yes No
直接检查null No No No Yes
直接检查undefined Yes No No Yes
跨窗口工作 Yes No No Yes
我们总结为一句口诀:如果你要判断的是基本数据类型或JavaScript内置对象,使用toString; 如果要判断的时自定义类型,请使用instanceof。