JavasSript基础之封装检测数据类型的方法

229 阅读3分钟

承接上一篇文章JS数据类型检测——四大常用方法和底层机制,这次我们来依据原理并参考jQuery源码,封装公共的数据类型检测方法

如果在日常项目中数据类型检测用的不多,那么说明

  • 你在项目中很少去封装,公共的插件,组件,方法库等
  • 你的项目很少去考虑隐藏的安全隐患,以及不可控的风险

jQuery中封装的一些方法很有借鉴意义

jQuery源码中的检测方法

image。png

这些方法与数据类型检测有关系

我们提取出来,看看这些属性都是什么作用,作用和结果都在注释当中,

//获取原型
var getProto = Object.getPrototypeOf,
    //声明一个空对象
    class2type = {},
    //->相当于Object.prototype.toString ,检测数据类型
    toString = class2type.toString, 
    //->Object.prototype.hasOwnProperty函数
    hasOwn = class2type.hasOwnProperty, 
    //->Function.prototype.toString ,函数.toString方法,把函数转换为字符串
    fnToString = hasOwn.toString, 
    //->把Object构造函数转换为字符串,相当于执行Object.toString(),结果为 “function Object(){[native code]}”
    ObjectFunctionString = fnToString.call(Object); 

这些方法和属性都是下面进行数据类型检测封装代码的前提条件

检测是否为一个函数

js中获取 <object> 的元素对象(跟flash有关,现在基本上已经被淘汰),在某些浏览器中,基于 typeof 检测这个对象,返回的也是 function ,所以要排除这个对象的干扰。现浏览器已基本上没有这个问题。 nodeType 是DOM元素都有节点类型,是一个数字

// 检测是否为一个函数
var isFunction = function isFunction(obj) {
    // In some browsers, typeof returns "function" for HTML <object> elements
    return typeof obj === "function" && typeof obj.nodeType !== "number";
};

检测是否为一个window对象

window 有一个特殊的地方, window 对象里面还有一个 window 属性,指向它本身,依据此原理可以写出这种函数

window.window===window //true
// 检测是否为一个window对象
var isWindow = function isWindow(obj) {
    // null & undefined 无法进行成员访问,排除这两个
    return obj != null && obj === obj.window;
};

null & undefined 无法进行成员访问,在成员访问之前,最好先排除掉。其他基本数据类型,浏览器可以进行装箱操作。

专门对所有数据类型进行检测的办法

jQuery中做了一个类似这样的操作:

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

建立数据类型检测的映射表,这样我们最后用 toString.call 方法检测的数据的时候,可以将结果作为属性名,属性值是对应的数据类型的字符串的小写,这样可以获取类似于 typeof 的效果

封装的逻辑规则:

  • null & undefined 直接返回对应的字符串
  • 原始值类型基于 typeof 检测
  • 对象类型(包含原始值的对象类型)基于 Object.prototype.toString.call 检测
var toType = function toType(obj) {
    // null&undefined直接返回对应的字符串
    // 原始值类型基于typeof检测
    // 对象类型「包含原始值的对象类型」基于toString.call检测
    if (obj == null) return obj + "";
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[toString.call(obj)] || "object" ://如果基于typeof返回是object|function,就用toString检测,映射表中没有,就返回object
        typeof obj;//剩下的原始值类型用typeof检测
};

image。png 上面还有一个问题,映射表里没有 'GeneratorFunction' 那么我们使用 toType 去解析的时候,返回的就是 'object' ,因为我们使用 toString.call(function*(){}) 的时候,返回的是 '[Obeject GeneratorFunction]' ,所以在映射表中找不到对应的结果

映射表没有GeneratorFunction字符串

一个解决方法就是在映射表用添加新的类型。

var typeArr = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol", "BigInt", "GeneratorFunction", "Set", "Map", "WeakSet", "WeakMap"];

如果不使用映射表,就用正则去解析

// 专门进行数据类型检测的办法 
var toType = function toType(obj) {
    if (obj == null) return obj + "";
    if (typeof obj !== "object" && typeof obj !== "function") return typeof obj;
    var reg = /^\[object ([0-9A-Za-z]+)\]$/,
        value = reg.exec(toString.call(obj))[1] || "object";//基于正则捕获,捕获第一组是大正则匹配结果,第二组是分组
    return value.toLowerCase();
};

其他检测方法

包括

  1. 检测是否为数组或者类数组
  2. 检测是否为纯粹的对象
  3. 检测是否为空对象
  4. 检测是否为数字(不论是字符串类型还是数字类型) 这些检测方法的原理和注意的点写在注释当中了
    // 检测是否为数组或者类数组
    var isArrayLike = function isArrayLike(obj) {
        var length = !!obj && "length" in obj && obj.length,//获取length
            type = toType(obj);
        if (isFunction(obj) || isWindow(obj)) return false;// 函数&window都有length属性,但是不是类数组
        return type === "array" || length === 0 ||
            typeof length === "number" && length > 0 && (length - 1) in obj;//并没有一个非常严谨的答案去证明是否是类数组,所以这种方法还算严谨
    };

    // 检测是否为纯粹的对象:直属类是Object 或者 obj.__proto__===Object.prototype(数组/正则等都不是)
    var isPlainObject = function isPlainObject(obj) {
        var proto, Ctor;
        if (!obj || toType(obj) !== "object") return false;
        proto = getProto(obj);//获取原型
        if (!proto) return true;//Object.create(null)创造出来的空对象没有原型
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;//检测当前原型上有没有私有属性,constructor并且将constructor返回.如果没有就返回false,如果有就获取
        return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;//fnToString.call(Ctor) === ObjectFunctionString说明此构造函数一定是Object的构造函数
    };

    // 检测是否为空对象
    var isEmptyObject = function isEmptyObject(obj) {
        if (obj == null) return false;
        var keys = Object.keys(obj);
        if (typeof Symbol !== "undefined") {//如果支持Symbol
            keys = keys.concat(Object.getOwnPropertySymbols(obj));//支持Symbol就把Symbol属性连接在一起
        }
        return keys.length === 0;
    };

    // 检测是否为数字(不论是字符串类型还是数字类型,可以转换为正确的数字)
    var isNumeric = function isNumeric(obj) {
        var type = toType(obj);
        return (type === "number" || type === "string") && !isNaN(+obj);//将其转化之后,不是NaN,那就说明是数字
    };

封装为自己的工具库

将以上方法封装为工具库

这样就可以基于 <script>requireimport 将模块导入

//utils.js
(function () {
    "use strict";
    var getProto = Object.getPrototypeOf,
        class2type = {},
        toString = class2type.toString,
        hasOwn = class2type.hasOwnProperty,
        fnToString = hasOwn.toString,
        ObjectFunctionString = fnToString.call(Object);
    
    // 专门进行数据类型检测的办法 
    var toType = function toType(obj) {...}

    // 检测是否为一个函数
    var isFunction = function isFunction(obj) {...}

    // 检测是否为一个window对象
    var isWindow = function isWindow(obj) {...}

    // 检测是否为数组或者类数组
    var isArrayLike = function isArrayLike(obj) {...}

    // 检测是否为纯粹的对象
    var isPlainObject = function isPlainObject(obj) {...};

    // 检测是否为空对象
    var isEmptyObject = function isEmptyObject(obj) {...};

    // 检测是否为数字
    var isNumeric = function isNumeric(obj) {...};

    /* 暴露API */
    var utils = {
        toType: toType,
        isFunction: isFunction,
        isWindow: isWindow,
        isArrayLike: isArrayLike,
        isPlainObject: isPlainObject,
        isEmptyObject: isEmptyObject,
        isNumeric: isNumeric
    };

    // 转移_的使用权
    var $_ = window._;
    utils.noConflict = function noConflict() {
        if (window._ === utils) {//如果本身就是我们自己的仓库,那就再转回去,不是的话,_的使用权就被转出
            window._ = $_;
        }
        return utils;
    };

    if (typeof window !== "undefined") {//直接挂载到window上
        window._ = window.$utils = utils;
    }
    if (typeof module === "object" && typeof module.exports === "object") {//commonjs导出
        module.exports = utils;
    }
})();

总结

toType 方法基于 typeofObject.prototype.toString.call ,可以检测出几乎所有的数据类型,重点理解