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]]方法,实现了则为函数,没实现就是对象。
  • 劣势:
  1. typeof null => "object"
  2. typeof function(){}=> "function"
  3. typeof 检测数组,对象、正则等都是object「不能细分对象」,函数除外。
  4. 基于 typeof 检测一个未被声明的对象,不会报错,结果是 ‘undefined’。
  5. 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 问题解析

javascript对象属性检测枚举 js中检测数据类型的方法_前端

  • 所有值在计算机都是按二进制存储的,运算也是基于二进制处理的
    • 整数运算一般不会有问题
    • 小数运算一般会出现问题
  • 浮点数转为二进制会出现 ‘无限循环 ’的情况,计算机底层存储的时候按照可以识别的最长位数存储,其余的干掉,所以浮点数存储的二进制本身就是去了精准度的结果。
    • 所以最后的运算结果也是缺乏精准度的,而且小数最后面的0 会省略,但凡不是0,都不会省略。
    • 解决办法:把小数变乘整数再运算,最后把运算结果再转为小数。
    • 浮点数的加法运算解决办法
    • javascript对象属性检测枚举 js中检测数据类型的方法_前端_02


💥 十进制转二进制

  • 整数:除以2取余,最后结果倒着拼接
  • 小数,乘以2,取整,一直到结果是1为止
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类似,也是非专业检测数据类型的,但是可以这样处理一下
  • 语法:[对象].constructor===[构造函数]
  • 优势:相对于instanceof来讲基本数据类型也可以处理,而且因为获取实例的constructor,实际上获取的是直接所属的类,所以在检测准确性上比instanceof还好一点
  • 劣势:constructor可以被随意修改
4. Object.prototype.toString.call
  • 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]是否为有效数字