承接上一篇文章JS数据类型检测——四大常用方法和底层机制,这次我们来依据原理并参考jQuery源码,封装公共的数据类型检测方法
如果在日常项目中数据类型检测用的不多,那么说明
- 你在项目中很少去封装,公共的插件,组件,方法库等
- 你的项目很少去考虑隐藏的安全隐患,以及不可控的风险
jQuery中封装的一些方法很有借鉴意义
jQuery源码中的检测方法
这些方法与数据类型检测有关系
我们提取出来,看看这些属性都是什么作用,作用和结果都在注释当中,
//获取原型
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检测
};
上面还有一个问题,映射表里没有
'GeneratorFunction'
那么我们使用 toType 去解析的时候,返回的就是 'object' ,因为我们使用 toString.call(function*(){}) 的时候,返回的是 '[Obeject 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();
};
其他检测方法
包括
- 检测是否为数组或者类数组
- 检测是否为纯粹的对象
- 检测是否为空对象
- 检测是否为数字(不论是字符串类型还是数字类型) 这些检测方法的原理和注意的点写在注释当中了
// 检测是否为数组或者类数组
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> , require , import 将模块导入
//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 方法基于 typeof 和 Object.prototype.toString.call ,可以检测出几乎所有的数据类型,重点理解