检测数据类型的四种办法(JQ源码分析)

102 阅读4分钟
  1. typeof

语法:typeof [val] 返回当前值对应的数据类型(STRING)

优势:检测基本类型值还是很准确的,而且操作起来方便

略势:

typeof null =>"object"
typeof 检测数组/对象/正则等都是 "object",所以无法对对象数据类型细分
  1. instanceof

语法:[val] instanceof 类 通过检测这个值是否属于这个类,从而验证是否为这个类型

优势:对于数组、正则、对象可以细分一下

略势:

基本数据类型无法基于它来进行检测
1 instanceof Number //false

检测的原理:只要在当前实例的__proto__出现这个类,检测结果都是TRUR
[] instanceof Array //true
[] instanceof Object //true

而且当改变prototype指向的时候,instanceof就检测不出来了
function Fn(){}
Fn.prototype = Array.prototype
let fn = new Fn()
fn instanceof Array //true
  1. constructor

和instanceof类似,也是非专业检测数据类型的,但是可以这样处理一下

语法:[val].constructor===类

相对于instanceof来讲基本类型也可以处理,而且因为获取实例的constructor实际上获取的是直接所属的类,所以在检测准确性上比instanceof还好一点

略势:constructor是可以随意被改动的

Array.prototype.constructor = null;
[].constructor = Array //false
  1. Object.prototype.toString.call([val])

在其它数据类型的内置类原型上有toString,但是都是用来转换为字符串的,只有Object基类原型上的toString是用来检测数据类型的

obj.toString() obj这个实例调用Object.prototype.toString执行,方法执行里面的THIS是当前操作的实例OBJ,此方法就是检测实例THIS的数据类型的,返回结果:"[object 所属的类]"

Object.prototype.toString.call([val])基于call强制改变方法中的this是[val],就相当于在检测val的数据类型 <=> ({}).toString.call([val])

是最强大的检测方案

那JQuery里面是如何检测数据类型的呢?

//首先创建一个对象
var class2type = {};

//获取对象的toString方法
var toString = class2type.toString; //=>Object.prototype.toString 检测数据类型的

//获取对象的hasOwnProperty方法
var hasOwn = class2type.hasOwnProperty; //=>Object.prototype.hasOwnProperty

//获取对象hasOwnProperty方法的toString方法,
//其实这里有点卖弄玄虚,直接Function.prototype.toString不就完了吗
var fnToString = hasOwn.toString; //=>Function.prototype.toString

//调取Function.prototype.toString方法,把this指向object,获取object的字符串
var ObjectFunctionString = fnToString.call(Object); //=>"function Object() { [native code] }"

var getProto = Object.getPrototypeOf;//获取当前值的原型

到此JQ要准备的一些工作做完了,下面开始它的检测实例

jQuery.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),
function (_i, name) {
    class2type["[object " + name + "]"] = name.toLowerCase();
}); 

这段代码想表达的就是把各个类型的实例类存到了class2type里面,展开就是这样的:

class2type={
    [object Array]: "array"
    [object Boolean]: "boolean"
    [object Date]: "date"
    [object Error]: "error"
    [object Function]: "function"
    [object Number]: "number"
    [object Object]: "object"
    [object RegExp]: "regexp"
    [object String]: "string"
    [object Symbol]: "symbol"
}

["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol"].forEach(function (name, _i) {
    class2type["[object " + name + "]"] = name.toLowerCase();
});

有人可能会有疑惑,为什么Number、String等这些基本数据类型的也要加进去,直接typeof就可以了啊?因为有可能这些基本数据类型是new出来的

let num = new Number(1);
let str = new String("haha");

这时得到的值也是对象,所以针对这种方式的基本数据类型也要加进去

好,那我们看看他是怎么检测的

// 检测数据类型的公共方法
function toType(obj) {
    // 传递的是null返回 "null"
    if (obj == null) {
        return obj + "";
    }
    // 引用数据类型,我们基于toString.call检测,基本类型直接typeof处理即可
    // -> toString.call(obj)  "[object Xxx]"
    //此时这个toString是上面准备工作的toString,并不是某个类自带的toString方法
    //由于用此方法检测出来的都是[object xxxx]样子的,符合上面的大集合分类规则,
    //如果上面集合没有,那就是普通object对象
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[toString.call(obj)] || "object" :
        typeof obj;
}

检测是否为数组或者类数组

function isArrayLike(obj) {
    // length:false或者length属性值
    // type:数据类型
    var length = !!obj && "length" in obj && obj.length,
        type = toType(obj);
 
    // 排除函数和WINDOW
    if (isFunction(obj) || isWindow(obj)) {
        return false;
    }
 
    // type === "array" 数组
    // length === 0 空的类数组
    // typeof length === "number" && length > 0 && (length - 1) in obj 有lengt属性并且为了保证有数字索引并且是递增的,基于length - 1最大索引是否在OBJ中来验证  “类数组”
    return type === "array" || length === 0 ||
        typeof length === "number" && length > 0 && (length - 1) in obj;
}

纯粹对象 obj.proto===Object.prototype

jQuery.isPlainObject = function (obj) {
    var proto, Ctor;
 
    // 不存在或者检测数据类型不是OBJECT则返回FALSE
    if (!obj || toString.call(obj) !== "[object Object]") {
        return false;
    }
 
    // 获取当前值的原型
    proto = getProto(obj);
 
    
    if (!proto) {
        return true;
    }
 
    
    Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
    return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString; //此条件成立说明 Ctor===Object
};

检测是否为空对象

jQuery.isEmptyObject = function (obj) {
    for (var name in obj) {
        // 如果可以进入FOR IN循环,说明对象是有属性的
        return false;
    }
    return true;
};

检测是否为一个Number类型:"1"也是Number类型

jQuery.isNumeric = function (obj) {
    var type = jQuery.type(obj);
    return (type === "number" || type === "string") &&
        !isNaN(obj - parseFloat(obj));
};