1

前言

在js中数据我们经常需要判断或者获取数据类型,大部分时候我们都是通过type加instanceof来组合判断数据类型来实现,大部分代码中对于数据类型的获取处理都比较丑陋,前段时间看了一下Q的源代码中对数据类型的判断与获取处理,看起来相当简洁也比较好用,这篇文章来进行一下发散。

typeof

在js中我们判断数据类型经常会用到typeof,比如判断一个数据是否是一个数字类型

var n = 99;
typeof(n) === 'number';    // true

这么做有一个缺点,typeof只能判断js中的基础数据类型undefined、String、Number、Boolean、Object。如果需要判断一个数据是不是Array类型,这个时候instanceof就派上用场了。

instanceof

官方对于instanceof说明: 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性(翻译成人话:判断对象指向构造函数名称是否与构造函数名一致),如图:

clipboard.png

判断一个数据是否是时间类型,一般我们都这样写:

var list = [];
list instanceof Date;    // false

instanceof有一个缺点,只能针对对象类型的数据进行处理,因为只有对象中才包含原型链prototype,当然平常我们用到的function数据类型也是对象的一种,还是一图解千言,看一下js中的数据类型大概明了。

clipboard.png

判断null的数据类型

在js中null也是Object中的一个子类型(关于null,可以看这篇文章),但是我们不能通过instanceof去获取,因为null中并没有原型链prototype,于是我们有了这样的代码:

var str = 'null';
str === null;    // false

基础版本获取数据类型

当我们并不知道数据类型,但是需要获这个数据的类型时,大部分童鞋的代码里面都是这样写的:

classString(obj) {
    if (obj && (obj.__proto__ || obj.prototype)) {
        if (obj instanceof Array) {
            return 'array';
        }
        if (obj instanceof Function) {
           return 'function';
        } 
        // 所有obj的衍生数据都判断一遍 ...
    } else if (obj === null) {
        return 'null';    // 返回字符串
    } else {
        return typeof(obj);
    }
}

这样的代码很繁杂,这么多的”if else“(俗称面条代码),既不美观,也不实用。

升级版本获取数据类型

function classString(obj) {
    return ({}).toString.call(obj);
}
classString(null);    // [object Null]
classString('string');    // [object String]
classString(function(){});    // [object Function]
classString(new Date());    // [object Function]

有两个关键方法:一个是call,另一个是Object.prototype.toString方法进行处理。

先看call方法简短描述:

fun.call(this, arg1, arg2, ...)
call: 调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)

再看一下关于call参数说明:

在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象,同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。

只有当this不存在上下文的时候,this才会指向全局对象(浏览器中就是window对象)。

”包装对象“,并不是一种数据类型,原始数据类型中:字符串、数字、布尔值可以转换成相应的Number、String、Boolean对应的原生对象(注意是对象,不是值),具体在call中的表现形式如下(左边等价于右边):

clipboard.png

再举个栗子?:

999 instanceof Number;    // false
new Number(999) instanceof Number;    // true

关于Object toString方法的简短说明:

默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中type是对象的类型。

这里的type并不是js5中基础数据类型相关的type,而是当前对象中__proto__中指向的构造函数名(关于__proto__

自定义数据类型

我们在开发的时候经常需要自定义数据,比如说我们自己创建了一个构建函数F,当我们获取数据类型的时候期待返回的结果为”[object F]“,代码如下

var F = function() {}
var f1 = new F();
classString(f1);    // [object Object]

期待的结果并未返回,原因是因为Object.prototype.toString方法只定义了自带的对象类型返回,EcmaScript关于Object toString 规范

clipboard.png

我们可以直接重定义Object.prototype.toString:

Object.prototype.toString = function(){
    // Do something ...
}

这并不是一种很好的行为,一方面容易造成全局污染,另一方面也不利于进行排错;所以还是老老实实的在方法中判断吧:

function classString(obj, customize) {
    if (customize && obj && obj.__proto__.constructor.name) {
        return '[object ' + obj.__proto__.constructor.name + ']';
    }
    return ({}).toString.call(obj);
}
var F = function() {}
var f1 = new F();
classString(f1, true);    // [object F]
classString(f1);    // [object Object]

为什么可以通过__proto__.constructor.name来获取构造函数名,object与function并不会造成混乱,object与function中的 proto 指向并不是相同的,这里不细讲,还是参考关于__proto__

优化返回格式

对于”[object type]“数据返回我们只需要获取type即可,可以通过正则表达获取type指,这里不做代码说明。

其他

1、关于call、toString方法平常都是信手拈来使用,没有深入探究过,探究起来和以前自己脑海中的理解还是有些不同的。
2、 如果项目中需要频繁的进行数据类型的判断与获取可以考虑进行封装,简单的处理typeof与instanceof已足够。
3、上述代码中只是简单的示例,部分地方并不是十分严谨。


参考资料

EcmaScript
MDN instanceof
MDN call
MDN types Array
undefined与null有什么区别
关于__proto__与prototype
EcmaScript Object toString


petruslaw
651 声望11 粉丝

你还没有成为真正的孙悟空,是因为你还没有遇见那个给你脚底三颗痣的人~