JS中数据类型检测的办法
- 1. typeof
- 检测是否为对象
- 0.1+0.2 问题解析
- 2. instanceof
- 3. constructor
- 4. Object.prototype.toString.call
- 其他快捷办法
typeof
和
instanceof
是专门用来检测数据类型的,是专业的。
1. typeof
-
typeof
是检测数据类型运算符。 - 语法 :
typeof [value]
- 返回一个字符串,字符串中包含了对应的数据类型。
- 优势:检测基本类型值(原始值类型,不含null)以及函数等值的检测还是很准确的,操作方便
- 根据计算机底层存储值的 “ 二进制值 ” 来检测的「性能会好一些」。
- 所有以‘000 ’开头的二进制,都识别为对象;然后在判断是否实现了 [[call]]方法,实现了则为函数,没实现就是对象。
- 劣势:
typeof null => "object"
typeof function(){}=> "function"
-
typeof
检测数组,对象、正则等都是object
「不能细分对象」,函数除外。 - 基于 typeof 检测一个未被声明的对象,不会报错,结果是 ‘undefined’。
typeof typeof [] -> "string"
。因为typeof [] => 'object', typeof 'object' => 'string'
。
检测是否为对象
if(val !==null &&(typeof val ==='object' || typeof val === "function")) { /* 成立则为对象*/ }
。if(val !==null && /^(object | function)$/i.test(typeof val)) { /* 成立则为对象*/ }
用正则实现。- 思考题:检测是否是标准普通对象(纯粹对象->
对象.__proto__ === Object.prototype
) 封装isPlaneObject
函数。
0.1+0.2 问题解析
- 所有值在计算机都是按二进制存储的,运算也是基于二进制处理的
- 整数运算一般不会有问题
- 小数运算一般会出现问题
- 浮点数转为二进制会出现 ‘无限循环 ’的情况,计算机底层存储的时候按照可以识别的最长位数存储,其余的干掉,所以浮点数存储的二进制本身就是去了精准度的结果。
- 所以最后的运算结果也是缺乏精准度的,而且小数最后面的0 会省略,但凡不是0,都不会省略。
- 解决办法:把小数变乘整数再运算,最后把运算结果再转为小数。
- 浮点数的加法运算解决办法
💥 十进制转二进制
- 整数:除以2取余,最后结果倒着拼接
- 小数,乘以2,取整,一直到结果是1为止
- 本意: 检测当前实例是否属于这个类;用来检测数据类型,仅是“临时拉来当壮丁”,所以存在很多弊端「可以基于instanceof细分对象类型」。
- 语法:
[实例] instanceof [构造函数]
- 原理:首先按照
[构造函数][Symbol.hasInstance]([实例])
- 如果存在这个属性方法,则方法执行返回的值就是最后检测的结果
- 如果不存在这个属性方法,则会查找当前[实例]的原型链,一直找到
Object.prototype
为止 - 如果查找中途,找到的某个原型等于"构造函数"的原型「即构造函数的原型出现在其原型链上」则返回结果是true,反之false
- 优势:对于数组、正则、对象可以细分一下
- 劣势:基本数据类型无法基于它来检测
- 检测原理:
- 构造函数
[Symbol.hasInstance](实例)
- 检测当前构造函数的原型
prototype
是否出现在,当前实例所处的原型链上__proto__
,如果能出现结果就是true - 在JS中原型链是可以改动的,所有结果不准确
- 所有实例的原型链最后都指向
Object.prototype
,所以实例 instacnceof Object
的结果都是true - 字面量方式创造的基本数据类型值是无法基于
instanceof
检测的「浏览器默认并不会把它转换为new的方式」,所以它本身不是对象,不存在__proto__
这个东西
- 和instanceof类似,也是非专业检测数据类型的,但是可以这样处理一下
- 语法:[对象].constructor===[构造函数]
- 优势:相对于instanceof来讲基本数据类型也可以处理,而且因为获取实例的constructor,实际上获取的是直接所属的类,所以在检测准确性上比instanceof还好一点
- 劣势:constructor可以被随意修改
- 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中创建一个值有两种方案:
- 字面量方式
let n = 100;
let obj1 = {}; - 构造函数方式 「不能 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]是否为有效数字