检测数据类型的四种办法
发布日期:2021-05-04 20:18:21 浏览次数:30 分类:技术文章

本文共 6152 字,大约阅读时间需要 20 分钟。

JS中数据类型检测的办法

1. typeof [value]

  • 语法 :typeof [value] :返回一个字符串,字符串中包含了对应的数据类型
  • 优势:检测基本类型值(原始值类型,不含null)以及函数等值的检测还是很准确的,操作方便
    • 原理:根据计算机底层存储值的 “ 二进制值 ” 来检测的「性能会好一些」
  • 劣势:
    1. typeof null => "object"
    2. typeof 检测数组,对象、正则等都是object,不能细分对象
    3. typeof typeof [] -> "string"

2. [实例] instanceof [构造函数]

  • 本意: 检测当前实例是否属于这个类;用来检测数据类型,仅是“临时拉来当壮丁”,所以存在很多弊端「可以基于instanceof细分对象类型」
  • 语法:[实例] instanceof [构造函数]
  • 原理:首先按照 [构造函数][Symbol.hasInstance]([实例])
    • 如果存在这个属性方法,则方法执行返回的值就是最后检测的结果
    • 如果不存在这个属性方法,则会查找当前[实例]的原型链,一直找到Object.prototype为止
    • 如果查找中途,找到的某个原型等于"构造函数"的原型「即构造函数的原型出现在其原型链上」则返回结果是true,反之false
  • 优势:对于数组、正则、对象可以细分一下
  • 劣势:基本数据类型无法基于它来检测
  • 检测原理:
    • 构造函数[Symbol.hasInstance](实例)
    • 检测当前构造函数的原型prototype是否出现在,当前实例所处的原型链上__proto__,如果能出现结果就是true
    • 在JS中原型链是可以改动的,所有结果不准确
    • 所有实例的原型链最后都指向Object.prototype,所以 实例 instacnceof Object的结果都是true
    • 字面量方式创造的基本数据类型值是无法基于 instanceof检测的「浏览器默认并不会把它转换为new的方式」,所以它本身不是对象,不存在__proto__这个东西

3. [对象].constructor===[构造函数]

  • 和instanceof类似,也是非专业检测数据类型的,但是可以这样处理一下
  • 语法:[val].constructor === 类
  • 优势:相对于instanceof来讲基本数据类型也可以处理,而且因为获取实例的constructor,实际上获取的是直接所属的类,所以在检测准确性上比instanceof还好一点
  • 劣势:constructor可以被随意修改

4. Object.prototype.toString.call([value])

  • 在其他数据类型的内置类原型上有toString,但是都是用来转换为字符串的 ,只有Object基类原型上的toString是用来检测数据类型的。
    • 所属构造函数的信息是根据 Symbol.toStringTag 获取的「有这个属性基于这个获取,没有浏览器自己计算」
  • obj.toString() obj这个实例调用Object.prototype.toString执行,方法执行里面的THIS是当前操作的实例OBJ,此方法就是检测实例THIS的数据类型的,返回的结果:"[object 所属类]" =>[万物皆对象,所属的类]
  • Object原型上的toString并不是用来转换为字符串的,而是用来检测数据类型的,
  • 检测的方法:执行Object.prototype.toString方法,因为this是谁,就检测谁的数据类型,所以通过call强制改变this是[val],就相当于在检测val的数据类型 <=> ({}).toString.call([val])
  • 这种方法是最强大的检测方案,准确率最高,就是代码稍稍冗余
  • let obj={name:‘zhufeng’};
    obj.toString -> Object.prototype.toString
    let arr=[];
    arr.toString -> Array.prototype.toString
    鸭子类型「原型上方法的借用」
    =>Object.prototype.toString.call(arr)
    =>({}).toString.call(arr)

JS中创建一个值有两种方案:

  1. 字面量方式
    let n = 100;
    let obj1 = {};
  2. 构造函数方式 「不能 new Symbol/new BigInt -> Object(symbol/bigint) 其他基本类型值也可以这样处理,但是都要排除null/undefined」
    let m = new Number(100);
    let obj2 = new Object();

对于基本数据类型,两种方式的结果是不一样的:

  • 字面量方式得到的是基本数据类型「特殊的实例」,而构造函数方式得到的是对象类型「正规的实例」
  • 对于引用数据类型,两种方式除了语法上的一些区别,没有本质的区别,获取的都是对应类的实例对象

其他快捷办法

  • Array.isArray([value]) :检测value是否是数组
  • isNaN([value]):检测[value]是否为有效数字

JQ部分源码分析

(function () {
var class2type = {
}; var toString = class2type.toString; //Object.prototype.toString 检测数据类型的 var hasOwn = class2type.hasOwnProperty; //Object.prototype.hasOwnProperty 检测是否私有属性的 var fnToString = hasOwn.toString; //Function.prototype.toString 把函数转换为字符串 var ObjectFunctionString = fnToString.call(Object); //=>"function Object() { [native code] }" var getProto = Object.getPrototypeOf; //获取当前对象的原型链__proto__ // 建立数据类型检测的映射表 { "[object Array]":"array",....} var mapType = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object", "Error", "Symbol", "BigInt"]; mapType.forEach(function (name) {
class2type["[object " + name + "]"] = name.toLocaleLowerCase(); }); // 检测数据类型的办法 var toType = function toType(obj) {
if (obj == null) {
// 传递的是 null/undefined return obj + ""; } // 基于字面量方式创造的基本数据类型,直接基于typeof检测即可「性能要高一些」; // 剩余的基于Object.prototype.toString.call的方式来检测,把获取的值到映射表中匹配,匹配结果是字符串对应的数据类型; return typeof obj === "object" || typeof obj === "function" ? class2type[toString.call(obj)] || "object" : typeof obj; }; // 检测是否为函数 var isFunction = function isFunction(obj) {
// typeof obj.nodeType !== "number" :防止在部分浏览器中,检测元素对象结果也是"function",但是它的nodeType=1,处理浏览器兼容问题 return typeof obj === "function" && typeof obj.nodeType !== "number"; }; // 检测是否为window对象 var isWindow = function isWindow(obj) {
// window.window===window 符合这个条件的就是window对象 return obj != null && obj === obj.window; }; // 检测是否为数组或者类数组 var isArrayLike = function isArrayLike(obj) {
// length存储的是对象的length属性值或者是false // type存储的是检测的数据类型 var length = !!obj && "length" in obj && obj.length, type = toType(obj); // window.length=0 && Function.prototype.length=0 if (isFunction(obj) || isWindow(obj)) return false; // type === "array" 数组 // length === 0 空的类数组 // 最后一个条件判断的是非空的类数组「有length属性,并且最大索引在对象中」 return type === "array" || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj; }; // 检测是否为纯粹的对象 例如:{} var isPlainObject = function isPlainObject(obj) {
var proto, Ctor; // 不存在或者基于toString检测结果都不是[object Object],那么一定不是纯粹的对象 if (!obj || toString.call(obj) !== "[object Object]") {
return false; } // 获取当前值的原型链「直属类的原型链」 proto = getProto(obj); // Object.create(null):这样创造的对象没有__proto__ if (!proto) return true; // Ctor存储原型对象上的constructor属性,没有这个属性就是false Ctor = hasOwn.call(proto, "constructor") && proto.constructor; // 条件成立说明原型上的构造函数是Object:obj就是Object的一个实例,并且obj.__proto__===Object.prototype return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString; }; // 检测是否为空对象 var isEmptyObject = function isEmptyObject(obj) {
// 排除非对象 if (obj == null) return false; if (typeof obj !== "object") return false; // 是一个对象「纯粹对象或者特殊对象都可以」 var keys = Object.keys(obj); if (hasOwn.call(Object, 'getOwnPropertySymbols')) {
// 兼容这个属性的情况下,我们再去拼接 keys = keys.concat(Object.getOwnPropertySymbols(obj)); } return keys.length === 0; }; // 检测是否为数字 var isNumeric = function isNumeric(obj) {
var type = toType(obj); return (type === "number" || type === "string") && !isNaN(+obj); }; // 暴露到外部 var utils = {
toType: toType, isFunction: isFunction, isWindow: isWindow, isArrayLike: isArrayLike, isPlainObject: isPlainObject, isEmptyObject: isEmptyObject, isNumeric: isNumeric }; if (typeof window !== "undefined") {
window._ = window.utils = utils; } if (typeof module === "object" && typeof module.exports === "object") {
module.exports = utils; }})();
上一篇:JS中的常用继承方式总结
下一篇:函数三种角色剖析

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2025年04月05日 03时49分13秒