js数据类型检测方法,你玩废了吗

270 阅读6分钟

话不多说,直接正文

前言

JS中的8种数据类型及区别:

基本类型(值类型): 保存在栈内存中

  1. Number(数字)
  2. String(字符串)
  3. Boolean(布尔)
  4. null(空)
  5. undefined(未定义)
  6. Symbol(符号,ES6新增) 创建唯一值 独立内存空间(Symbol('1')===Symbol('1') //false)
  7. BigInt(要创建BigInt,只需要在数字末尾追加n即可;ES10新增)

引用类型(复杂数据类型): 保存在堆内存中

  • Object(对象)。其他还有Array(数组)、Date(日期)、RegExp(正则表达式)。

Symbol类型:是为了解决属性名冲突的问题,需要定义私有属性的时候,用  Symbol 表示对象内部状态,可以很好地隔离用户数据和程序状态。有了它,我们就不再需要某些命名约定了。

BigInt类型:在JS中,所有的数字都以双精度64位浮点格式表示,这导致JS中的Number无法精确表示非常大的整数,它会将非常大的整数四舍五入,超出此范围的整数值都可能失去精度;BigInt用于当整数值大于Number数据类型支持的范围时,这种数据类型允许我们安全地对 大整数 执行算术操作。

为什么基本类型在栈内存 引用类型在堆内存?

1、基本数据类型值与值之间是独立存在,修改一个变量不会影响其他的变量。
对象是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间, 而变量保存的是对象的引用地址,如果两个变量保存的是同一个对象引用, 则这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响。

2、因为引用类型数据占用的空间比较大,如果放在栈中,会影响到调用栈的执行上下文的切换效率,也可能导致栈空间不足;而堆空间比较大,能存放很多大的数据。

在操作系统中,内存被分为栈区和堆区:

  • 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区内存一般由开发者分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

垃圾回收:栈内存变量基本上用完就回收了,而推内存中的变量因为存在很多不确定的引用,只有当所有调用的变量全部销毁之后才能回收。

检测 js数据类型有几种方法呢

1、typeof

返回数据类型的字符串表达 "xxx" typeof 1 //"number"

缺点:
1.不能区分null,返回"object"
2.不能细分对象(普通对象、数组对象、正则对象、日期对象)等,都返回"object"

image.png

typeof function(){}   // 'function'
typeof NaN    //'number'

那么问题来了,对象分为普通对象、数组对象、正则对象、日期对象等,所以 对象 数组 正则 返回object情有可原,为什么null会返回是object呢

原来啊,所有计算机底层是用二进制存储的,typeof也是用二进制判断的,因为null存的是000,而typeof把以3个0开头的都判定为对象,所以返回object

typeof原理: 

不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息。

  • 000: 对象
  • 010: 浮点数
  • 100: 字符串
  • 110: 布尔
  • 1: 整数

2、instanceof

检测当前实例是否属于这个类

因为typeof不能将对象细分,都返回object,而instanceof刚好可以解决typeof的不足。

image.png

然后又试了下

image.png

啊 这,为什么这也是true呢,原因如下:

instanceof底层机制:只要当前类出现在实例的原型链上,结果都是true
我们来看一下arr:

image.png

image.png

我们发现Array和Object都出现在了arr的原型链上,所以都为true

优点:能够将对象细分,区分普通对象,数组,正则,日期等

缺点:
1.Number,Boolean,String等基本数据类型不能判断,输出false
2.由于我们可以修改原型的指向,所以检测出来的结果是不准确的

instanceof原理:

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

instanceof 的工作原理就是将s每一层的__proto__与 F.prototype 做比较找到相同返回 true,至最后一层仍没有找到返回 false

手写一个instanceof:
function instanceOf(left, right) { //left 表示左表达式,right 表示右表达式
  let proto = left.__proto__       // 取 left 的隐式原型
  let prototype = right.prototype  // 取 right 的显示原型
  while(proto) {
      // 这里重点:当 prototype 严格等于 proto时,返回 true
      if(proto === prototype) return true
      proto = proto.__proto__
  }
  return false   // Object.prototype.__proto__===null 表示原型链顶端
}

3、constructor

image.png

因为arr的constructor就是Array,所以只有第一行输出true

image.png

那么用constructor检测基本数据类型试一下:发现可行

image.png

缺点:由于我们可以修改constructor的指向,所以检测出来的结果是不准确的

例如:

image.png

4、Object.prototype.toString.call()

能精准判断数据类型。 返回模板:"[object xxx]"
Object.prototype.toString不是转换为字符串,是返回当前实例所属类的信息

image.png

Object.prototype.toString.call(function(){})  // '[object Function]'
检测原理
首先,这句话的意思是让我们用Object原型上的toString方法作用在传入的obj的上下文中(通过call将this指向obj),
那么我们知道数组本身也有toString()方法,那我们为什么非要用Object上的呢?

.toString()返回这个对象的字符串表现
比如:console.log([1,2].toString());//1,2 返回的是数组对象的字符串表现
Array、function等类型作为Object的实例,都各自重写了自己的toString方法;
我们要得到对象的具体类型,需要调用Object的原型的 `未被重写` 的toString()方法;

Object.prototype.toString([]) //'[object Object]'

为什么要加 .call()?

Object.prototype返回的是原型的字符串的表现
原型       Obj
实例  Object.prototype
如果不加call()的话返回的就是原型obj的字符串的表现,
加call()其实就是把call中的参数传入原型obj中,指向被call的对象;
所以我们在使用时要加call()
实现一个检测方法:
function typeOf(obj) {
    let res = Object.prototype.toString.call(obj).split(' ')[1];
    res = res.substring(0,res.length-1).toLowerCase();
    return res;
}
console.log(typeOf([]))  //array
console.log(typeOf({}))  //object
console.log(typeOf(new Date))  //date

增强版:

image.png

image.png

当然判断数组还有:Array.isArray() 方法