js中的数据类型检测

143 阅读3分钟

前言

金三银四已经到来,面试的人应该很多,上战场前对基础知识进行整理与回顾,于时在这整理了一下js中的数据类型检测的方法,以及各个方法的利弊,同时对数据类型检测进行统一的封装,废话不说,开搞!!!

js中的数据类型

  • 原始数据类型
    • number (数字)
    • string (字符串)
    • boolean (布尔)
    • null (空)
    • undefined (未定义)
    • symbol (符号)
    • bigint (大数)
  • 对象类型
    • 标准普通对象 object
    • 标准特殊对象 Array/RegExp/Error/Math/ArrayBuffer/DataView/Set/Map...
    • 非标准特殊对象 Number/Boolean/String/Symbol/BigInt...
    • 可调用对象(实现call方法)function 具体对数据类型的解释再此不再赘述,可见我的这篇文章

js中的数据检测类型的方法

上文中我们说明了数据类型,那么有哪些方法检测数据类型呢?
typeof
instanceof
constructor
Object.prototype.toString
接下来我们会一一介绍其中利弊,同时也会封装一个检测数据类型的方法

typeof

可以通过typeof进行类型的判断返回的是个字符串类型,见下图

typeof.png
通过上面的图可以看出typeof是不能细分对象类型的,原型数据类型(不含null)已经函数类型的检测使用typeof进行检测还是不错的

instanceof

用法:[实例] instanceof [构造函数]
本意: 不是用来检测数据类型,而是检测当前实例是否属于这个类,所以存在很多弊端,接下来我们就来说一说instanceof的原理和弊端

console.log([] instanceof Array)         // true  
console.log([] instanceof Object)        // true

从上面的打印结果大家可以想一想为什么第二个式子也是打印true呢?想要知道原因需要了解instanceof的原理,是按照什么样的规则运行的

原理:

首先按照[构造函数][Symbol.hasInstance][实例],如果存在过这个属性方法,则方法执行返回的值就是最后的检测结果;如果不存在这个属性方法,则会查找当前[实例]的原型链(一直找到Object.ptototype为止),如果查找中途,找到某个原型等于[构造函数]的原型,则返回结果为true,反之为false

缺点

1.因为原型可以被重定向,所以检测结果不一定准确
2.原始值类型使用instanceof是无法检测的
数组.png
从上图可以看出按照所说的查询原理,[]的原型链上分别找到了Array与Object时所以上面的两个打印结果都是true,也是正式因为它是按照原型链的查找发方式判断的原因,又因为原型是可以重定向的,所以检测结果不一定准确,我们可以手动更改原型,如下:

function Fn() {}
Fn.prototype = []
let f = new Fn
// 正常情况下f肯定不是个数组
console.log(f instanceof Array)    // true
重写instanceof方法
function instsance_of (obj, Ctor) {
  // 排除null,undefined,不具有原型链
  if (obj == null) return false
  // 排除Ctor不具有prototype属性;例如箭头函数
  if (Ctor == null) throw new TypeError("Rigth-hand side of 'instanceof' id not an object")
  if (!Ctor.prototype) throw new TypeError("Fuction has non-object prototype 'undefined' in instanceof check")
  if (typeof Ctor !== "function") throw new TypeError("Rigth-hand side of 'instanceof' id not callable")


  // 原型值类型忽略
  if (!/^(object|function)$/.test(typeof obj)) return false

  // 先检查是否具有Symbol.hasInstance这个属性
  if(typeof Ctor[Symbol.hasInstance] === 'function') return Ctor[Symbol.hasInstance](obj)

  // 最后按照原型链查找
  let property = Object.getPrototypeOf(obj)  // Object.getPrototypeOf获取原型链
  while (property) {
    if(property === Ctor.prototype) return true
    property = Object.getPrototypeOf(property)
  }
  return false
}
let res = instsance_of([10, 20], Array)
console.log(res)

OK!!!以上就是对instanceof从原理方面进行的封装重写,接下来我们继续

constructor

let value = []
console.log(value.constructor === Array)         // true
console.log(value.constructor === Object)       // false


let value = 1
console.log(value.constructor === Number)       // true

从上式可以看出constructor本意并不是用来检测数据类型,因为也是可以手动更改constructor的所以也不是很准确,但是它相对instanceof可以检测基本数据类型,再项目中也是不推荐使用的!!!

Object.prototype.toString

原理:

首先找到Object.prototype.toString方法,把toString执行之后,让方法中的this变为要检测的这个值,toString内部会返回对应的this的数据类型信息“[object ?]”
这里的'?'是什么取决于Symbol.toStringTag这个属性,如果存在这个属性,属性值是啥,‘?’就是啥,一般是返回所属的构造和函数信息

let classType = {}
toString = classType.toString      // 这样写后面可以直接用toString.call()进行检测,更为方便

let fn = function*() {}
console.log(toString.call(fn))     // "[object GeneratorFunction]"

我们一起看下fn[Symbol.toStringTag]是个什么东西,验证是否按照上面所说的原理进行检测的,见下图

原理.png

封装一个公共的数据类型方法

上面已经简述了平常开发中常用的数据类型检测的方法,各有利弊,接下来会封装一个公共的数据类型的方法,后续会继续向这个方法中添加常用的方法(深浅拷贝[后续会更新],防抖节流),来丰富这个方法库,可用于日常的开发中,开整!!!

var calss2type = {}
toString = calss2type.toString  // Object.getPrototype.toString 检测数据类型

// 创建一个数据类型检测的映射表:toString.call检测结果作为属性名,属性值对应的数据类型(转换为雄小写)
var typeArr = ['Boolean', 'Number', 'String', 'Function', 'Array', 'Date', 'RegExp', 'Object', 'Error', 'Symbol', 'BigInt', 'GeneratorFunction', 'Set', 'Map']
typeArr.forEach(function (name) {
  calss2type["[object"+ name + "]"] = name.toLocaleLowerCase()
})

// 专门进行数据检测的方法
var toType = function toType (obj) {
  // null与undefined直接返回对应的字符串
  // 原始值类型基于typeof检测
  // 对象类型[包含原始值的对象类型]基于toStrting.call检测
  if (obj == null) return obj + ''
  return typeof obj === 'object' || typeof obj === 'function' ?
    calss2type[toString.call(obj)] || 'object' :
    typeof obj
}

以上就是对数据类型检测的封装,看过jquery源码的同学看到这是不是很眼熟,对的,这就jq中封装的方法,接下里我们稍做改变,如下:

// 不用映射表
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.toLocaleLowerCase()
}

上面的两种方法均可实现数据类型检测,无论复杂数据类型还是简单数据类型,都能够返回对用的数据类型,笔记暂时就写道这吧,后续会更新深浅拷贝,望关注!!!最后还是送上那句话

夫学须静也,才须学也,非学无以广才,非志无以成学。淫慢则不能励精,险躁则不能治性。年与时驰,意与日去,遂成枯落